@mindbase/express-common 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.
Files changed (49) hide show
  1. package/dist/index.d.mts +363 -0
  2. package/dist/index.mjs +2468 -0
  3. package/dist/index.mjs.map +1 -0
  4. package/package.json +23 -11
  5. package/bin/mindbase.ts +0 -52
  6. package/commands/precache.ts +0 -54
  7. package/core/app.ts +0 -200
  8. package/core/module/CreateModule.ts +0 -38
  9. package/core/module/FindPackageRoot.ts +0 -58
  10. package/core/module/GetModulePath.ts +0 -58
  11. package/core/state.ts +0 -72
  12. package/feature/cron/CronManager.ts +0 -63
  13. package/feature/scanner/FileScanner.ts +0 -288
  14. package/index.ts +0 -10
  15. package/ipipfree.ipdb +0 -0
  16. package/middleware/Cors.ts +0 -17
  17. package/middleware/IpParser.ts +0 -81
  18. package/middleware/UaParser.ts +0 -50
  19. package/routes/Doc.route.ts +0 -118
  20. package/tests/Cors.test.ts +0 -34
  21. package/tests/Dayjs.test.ts +0 -24
  22. package/tests/FileScanner.test.ts +0 -85
  23. package/tests/GetModulePath.test.ts +0 -32
  24. package/tests/IpParser.test.ts +0 -72
  25. package/tests/Logger.test.ts +0 -68
  26. package/tests/UaParser.test.ts +0 -41
  27. package/tsconfig.json +0 -9
  28. package/types/DocTypes.ts +0 -111
  29. package/types/index.ts +0 -19
  30. package/utils/ComponentRegistry.ts +0 -34
  31. package/utils/DatabaseMigration.ts +0 -121
  32. package/utils/Dayjs.ts +0 -16
  33. package/utils/DocManager.ts +0 -274
  34. package/utils/HttpServer.ts +0 -41
  35. package/utils/InitDatabase.ts +0 -149
  36. package/utils/InitErrorHandler.ts +0 -71
  37. package/utils/InitExpress.ts +0 -35
  38. package/utils/Logger.ts +0 -206
  39. package/utils/MiddlewareRegistry.ts +0 -14
  40. package/utils/ProjectInitializer.ts +0 -283
  41. package/utils/RouteParser.ts +0 -408
  42. package/utils/RouteRegistry.ts +0 -66
  43. package/utils/SchemaMigrate.ts +0 -73
  44. package/utils/SchemaSync.ts +0 -47
  45. package/utils/TSTypeParser.ts +0 -455
  46. package/utils/Validate.ts +0 -25
  47. package/utils/ZodSchemaParser.ts +0 -420
  48. package/vitest.config.ts +0 -18
  49. package/zod/Doc.schema.ts +0 -9
@@ -1,274 +0,0 @@
1
- import Database from "better-sqlite3";
2
- import { Database as DatabaseType } from "better-sqlite3";
3
- import * as path from "path";
4
- import * as fs from "fs";
5
- import { RouteInfo, ModuleItem, DocDatabaseConfig } from "../types/DocTypes";
6
- import logger from "./Logger";
7
-
8
- /**
9
- * 文档管理器
10
- * 管理文档数据,与独立的 SQLite 数据库交互
11
- */
12
- export class DocManager {
13
- private db: DatabaseType | null = null;
14
- private config: DocDatabaseConfig;
15
-
16
- constructor(config: DocDatabaseConfig) {
17
- this.config = config;
18
- this.init();
19
- }
20
-
21
- /**
22
- * 初始化数据库
23
- */
24
- public init(): void {
25
- try {
26
- // 确保数据库目录存在
27
- const dbDir = path.dirname(this.config.path);
28
- if (!fs.existsSync(dbDir)) {
29
- fs.mkdirSync(dbDir, { recursive: true });
30
- }
31
-
32
- // 连接数据库
33
- this.db = new Database(this.config.path);
34
-
35
- // 创建表结构
36
- this.createTables();
37
- } catch (error) {
38
- logger.error("文档数据库初始化失败:", error);
39
- this.db = null;
40
- }
41
- }
42
-
43
- /**
44
- * 创建表结构
45
- */
46
- private createTables(): void {
47
- if (!this.db) return;
48
-
49
- // 创建 doc_routes 表
50
- this.db.exec(`
51
- CREATE TABLE IF NOT EXISTS doc_routes (
52
- id INTEGER PRIMARY KEY AUTOINCREMENT,
53
- module TEXT NOT NULL,
54
- method TEXT NOT NULL,
55
- path TEXT NOT NULL,
56
- full_path TEXT NOT NULL,
57
- summary TEXT,
58
- description TEXT,
59
- request_schema TEXT,
60
- middlewares TEXT,
61
- file_path TEXT NOT NULL,
62
- created_at INTEGER NOT NULL,
63
- updated_at INTEGER NOT NULL,
64
- UNIQUE(module, method, path)
65
- );
66
- `);
67
-
68
- // 创建索引
69
- this.db.exec(`
70
- CREATE INDEX IF NOT EXISTS idx_doc_routes_module ON doc_routes(module);
71
- CREATE INDEX IF NOT EXISTS idx_doc_routes_full_path ON doc_routes(full_path);
72
- `);
73
- }
74
-
75
- /**
76
- * 保存路由信息
77
- * @param routeInfo 路由信息
78
- * @param apiPrefix API 前缀
79
- * @param moduleName 模块名称
80
- */
81
- public saveRoute(routeInfo: RouteInfo, apiPrefix: string = "/api", moduleName: string): void {
82
- if (!this.db) return;
83
-
84
- try {
85
- // 计算完整路径
86
- const fullPath = `${apiPrefix}/${moduleName}${routeInfo.path}`.replace(/\/+/g, "/");
87
-
88
- // 准备数据
89
- const now = Date.now();
90
- const data = {
91
- module: moduleName,
92
- method: routeInfo.method,
93
- path: routeInfo.path,
94
- full_path: fullPath,
95
- summary: routeInfo.summary,
96
- description: routeInfo.description,
97
- request_schema: routeInfo.requestSchema ? JSON.stringify(routeInfo.requestSchema) : null,
98
- middlewares: routeInfo.middlewares ? JSON.stringify(routeInfo.middlewares) : null,
99
- file_path: routeInfo.filePath,
100
- created_at: now,
101
- updated_at: now,
102
- };
103
-
104
- // 插入或更新数据
105
- const stmt = this.db.prepare(`
106
- INSERT INTO doc_routes (
107
- module, method, path, full_path, summary, description,
108
- request_schema, middlewares, file_path, created_at, updated_at
109
- ) VALUES (
110
- @module, @method, @path, @full_path, @summary, @description,
111
- @request_schema, @middlewares, @file_path, @created_at, @updated_at
112
- ) ON CONFLICT(module, method, path) DO UPDATE SET
113
- full_path = @full_path,
114
- summary = @summary,
115
- description = @description,
116
- request_schema = @request_schema,
117
- middlewares = @middlewares,
118
- file_path = @file_path,
119
- updated_at = @updated_at
120
- `);
121
-
122
- stmt.run(data);
123
- } catch (error) {
124
- logger.error("保存路由信息失败:", error);
125
- }
126
- }
127
-
128
- /**
129
- * 获取模块列表
130
- * @returns 模块列表
131
- */
132
- public getModules(): ModuleItem[] {
133
- if (!this.db) return [];
134
-
135
- try {
136
- const stmt = this.db.prepare(`
137
- SELECT module, COUNT(*) as count
138
- FROM doc_routes
139
- GROUP BY module
140
- ORDER BY module
141
- `);
142
-
143
- return stmt.all() as ModuleItem[];
144
- } catch (error) {
145
- logger.error("获取模块列表失败:", error);
146
- return [];
147
- }
148
- }
149
-
150
- /**
151
- * 获取路由列表
152
- * @param module 模块名称(可选)
153
- * @returns 路由列表
154
- */
155
- public getRoutes(module?: string): RouteInfo[] {
156
- if (!this.db) return [];
157
-
158
- try {
159
- let query = `
160
- SELECT id, module, method, path, full_path, summary, description,
161
- request_schema, middlewares, file_path, created_at, updated_at
162
- FROM doc_routes
163
- `;
164
-
165
- const params: any = {};
166
-
167
- if (module) {
168
- query += " WHERE module = @module";
169
- params.module = module;
170
- }
171
-
172
- query += " ORDER BY module, method, path";
173
-
174
- const stmt = this.db.prepare(query);
175
- const rows = stmt.all(params) as any[];
176
-
177
- // 转换数据格式
178
- return rows.map((row) => ({
179
- id: row.id,
180
- module: row.module,
181
- method: row.method,
182
- path: row.path,
183
- fullPath: row.full_path,
184
- summary: row.summary,
185
- description: row.description,
186
- requestSchema: row.request_schema ? JSON.parse(row.request_schema) : undefined,
187
- middlewares: row.middlewares ? JSON.parse(row.middlewares) : undefined,
188
- filePath: row.file_path,
189
- createdAt: row.created_at,
190
- updatedAt: row.updated_at,
191
- }));
192
- } catch (error) {
193
- logger.error("获取路由列表失败:", error);
194
- return [];
195
- }
196
- }
197
-
198
- /**
199
- * 获取路由详情
200
- * @param id 路由 ID
201
- * @returns 路由详情
202
- */
203
- public getRouteById(id: number): RouteInfo | null {
204
- if (!this.db) return null;
205
-
206
- try {
207
- const stmt = this.db.prepare(`
208
- SELECT id, module, method, path, full_path, summary, description,
209
- request_schema, middlewares, file_path, created_at, updated_at
210
- FROM doc_routes
211
- WHERE id = @id
212
- `);
213
-
214
- const row = stmt.get({ id }) as any;
215
-
216
- if (!row) return null;
217
-
218
- // 转换数据格式
219
- return {
220
- id: row.id,
221
- module: row.module,
222
- method: row.method,
223
- path: row.path,
224
- fullPath: row.full_path,
225
- summary: row.summary,
226
- description: row.description,
227
- requestSchema: row.request_schema ? JSON.parse(row.request_schema) : undefined,
228
- middlewares: row.middlewares ? JSON.parse(row.middlewares) : undefined,
229
- filePath: row.file_path,
230
- createdAt: row.created_at,
231
- updatedAt: row.updated_at,
232
- };
233
- } catch (error) {
234
- logger.error("获取路由详情失败:", error);
235
- return null;
236
- }
237
- }
238
-
239
- /**
240
- * 清空所有路由数据
241
- */
242
- public clearAllRoutes(): void {
243
- if (!this.db) return;
244
-
245
- try {
246
- this.db.exec("DELETE FROM doc_routes");
247
- } catch (error) {
248
- logger.error("清空路由数据失败:", error);
249
- }
250
- }
251
-
252
- /**
253
- * 关闭数据库连接
254
- */
255
- public close(): void {
256
- if (this.db) {
257
- this.db.close();
258
- this.db = null;
259
- logger.info("文档数据库连接已关闭");
260
- }
261
- }
262
- }
263
-
264
- // 导出单例实例
265
- export let docManager: DocManager;
266
-
267
- /**
268
- * 初始化文档管理器
269
- * @param config 配置
270
- */
271
- export function initDocManager(config: DocDatabaseConfig): DocManager {
272
- docManager = new DocManager(config);
273
- return docManager;
274
- }
@@ -1,41 +0,0 @@
1
- import { Express } from "express";
2
- import logger from "./Logger";
3
-
4
- /**
5
- * 启动 HTTP 服务
6
- * @param app Express 实例
7
- * @param port 监听端口
8
- * @returns Promise<void>
9
- * @throws 如果服务器启动失败
10
- */
11
- export async function startServer(app: Express, port: number): Promise<void> {
12
- if (!app) {
13
- throw new Error("Express 实例不能为空");
14
- }
15
-
16
- if (!port || typeof port !== "number" || port <= 0 || port > 65535) {
17
- throw new Error(`无效的端口号: ${port}`);
18
- }
19
-
20
- return new Promise<void>((resolve, reject) => {
21
- try {
22
- const server = app.listen(port, () => {
23
- resolve();
24
- });
25
-
26
- // 处理服务器错误
27
- server.on("error", (error) => {
28
- logger.error("服务器启动失败:", error);
29
- reject(new Error(`服务器启动失败:${error instanceof Error ? error.message : String(error)}`));
30
- });
31
-
32
- // 处理服务器关闭
33
- server.on("close", () => {
34
- logger.info("服务器已关闭");
35
- });
36
- } catch (error) {
37
- logger.error("服务器启动异常:", error);
38
- reject(new Error(`服务器启动异常:${error instanceof Error ? error.message : String(error)}`));
39
- }
40
- });
41
- }
@@ -1,149 +0,0 @@
1
- import path from "path";
2
- import fs from "fs";
3
- import { drizzle } from "drizzle-orm/better-sqlite3";
4
- import { migrate } from "drizzle-orm/better-sqlite3/migrator";
5
- import Database from "better-sqlite3";
6
- import { AppState } from "../core/state";
7
- import { ScanResult } from "../types/Index";
8
- import logger from "./Logger";
9
-
10
- /**
11
- * 封装细节:从扫描结果中提取 Schema 并初始化数据库
12
- * @param state 应用状态
13
- * @param scannedResults 扫描结果
14
- * @returns 数据库实例
15
- * @throws 如果数据库设置失败
16
- */
17
- export function setupDatabase(state: AppState, scannedResults: ScanResult[]) {
18
- try {
19
- if (!state) {
20
- throw new Error("应用状态对象不能为空");
21
- }
22
-
23
- if (!Array.isArray(scannedResults)) {
24
- throw new Error("扫描结果必须是一个数组");
25
- }
26
-
27
- const schemas: Record<string, any> = {};
28
- for (const item of scannedResults) {
29
- if (item.type === "schema" && item.allExports) {
30
- try {
31
- Object.assign(schemas, item.allExports);
32
- } catch (e) {
33
- logger.warn(`处理 Schema 文件失败: ${item.filePath}`, e);
34
- }
35
- }
36
- }
37
- state.schemas = schemas;
38
-
39
- logger.startup("扫描", `提取到 ${Object.keys(schemas).length} 个 Schema 定义`);
40
-
41
- return initDatabase({
42
- schemas,
43
- state,
44
- });
45
- } catch (error) {
46
- logger.error("数据库设置失败:", error);
47
- throw new Error(`数据库设置失败:${error instanceof Error ? error.message : String(error)}`);
48
- }
49
- }
50
-
51
- /**
52
- * 初始化 SQLite 数据库
53
- * @param options 配置选项
54
- * @returns 数据库实例
55
- * @throws 如果数据库初始化失败
56
- */
57
- export function initDatabase(options: { schemas: Record<string, any>; dbPath?: string; state?: AppState }): any {
58
- if (!options) {
59
- throw new Error("初始化选项不能为空");
60
- }
61
-
62
- try {
63
- const currentDir = process.cwd();
64
- const dbPath = options.dbPath || options.state?.options?.database?.path || "./data/app.db";
65
- const absoluteDbPath = path.resolve(currentDir, dbPath);
66
- const dbDir = path.dirname(absoluteDbPath);
67
-
68
- logger.startup("数据库", `初始化:路径=${absoluteDbPath}`);
69
-
70
- // 确保目录存在
71
- try {
72
- if (!fs.existsSync(dbDir)) {
73
- logger.startup("数据库", `创建目录:${dbDir}`);
74
- fs.mkdirSync(dbDir, { recursive: true });
75
- }
76
- } catch (e) {
77
- throw new Error(`创建数据库目录失败:${e instanceof Error ? e.message : String(e)}`);
78
- }
79
-
80
- // 初始化 SQLite 数据库
81
- let sqlite: Database.Database;
82
- try {
83
- sqlite = new Database(absoluteDbPath);
84
- } catch (e) {
85
- throw new Error(`连接数据库失败:${e instanceof Error ? e.message : String(e)}`);
86
- }
87
-
88
- // 推荐的 SQLite 性能设置
89
- try {
90
- sqlite.pragma("journal_mode = WAL");
91
- sqlite.pragma("synchronous = NORMAL");
92
- } catch (e) {
93
- logger.warn("设置 SQLite 性能参数失败:", e);
94
- // 继续执行,不中断初始化
95
- }
96
-
97
- // 初始化 Drizzle
98
- let db: any;
99
- try {
100
- db = drizzle(sqlite, { schema: options.schemas || {} });
101
- } catch (e) {
102
- // 关闭 SQLite 连接
103
- try {
104
- sqlite.close();
105
- } catch (closeError) {
106
- logger.warn("关闭数据库连接失败:", closeError);
107
- }
108
- throw new Error(`初始化 Drizzle ORM 失败:${e instanceof Error ? e.message : String(e)}`);
109
- }
110
-
111
- // 如果提供了状态对象,更新它
112
- if (options.state) {
113
- options.state.db = db;
114
- }
115
-
116
- return db;
117
- } catch (error) {
118
- logger.error("数据库初始化失败:", error);
119
- throw new Error(`数据库初始化失败:${error instanceof Error ? error.message : String(error)}`);
120
- }
121
- }
122
-
123
- /**
124
- * 处理数据库表结构迁移
125
- * @param db 数据库实例
126
- * @param schemas 数据库 schema 定义
127
- * @throws 如果迁移失败
128
- */
129
- export async function handleDatabaseMigration(db: any, schemas: Record<string, any>) {
130
- try {
131
- if (!db) {
132
- throw new Error("数据库实例不能为空");
133
- }
134
-
135
- if (!schemas) {
136
- throw new Error("数据库 schema 定义不能为空");
137
- }
138
-
139
- // 只提醒,不自动执行
140
- logger.info("🔔 数据库表结构检查完成");
141
- logger.info(" 如需同步数据库结构,请执行:");
142
- logger.info(" npx drizzle-kit push");
143
-
144
- logger.debug("数据库迁移提醒已显示");
145
- } catch (error) {
146
- logger.error("数据库迁移提醒失败:", error);
147
- throw new Error(`数据库迁移提醒失败:${error instanceof Error ? error.message : String(error)}`);
148
- }
149
- }
@@ -1,71 +0,0 @@
1
- import { Request, Response, NextFunction, Express } from "express";
2
- import { ZodError } from "zod";
3
- import logger from "./Logger";
4
-
5
- /**
6
- * 检测是否为 SQL 相关错误(表/字段不存在)
7
- */
8
- function isSQLError(err: any): boolean {
9
- if (!err) return false;
10
- const msg = err.message || err.toString() || "";
11
- return /no such table|table.*does not exist|column.*does not exist|database schema has changed/i.test(msg);
12
- }
13
-
14
- /**
15
- * 封装细节:初始化错误处理和 404 中间件
16
- */
17
- export function setupErrorHandlers(app: Express, logging?: boolean) {
18
- // 404 处理
19
- app.use((req: Request, res: Response) => {
20
- res.status(404).json({
21
- code: 404,
22
- data: null,
23
- msg: "接口不存在",
24
- error: "Not Found",
25
- fields: {},
26
- });
27
- });
28
-
29
- // 错误处理
30
- app.use((err: any, req: Request, res: Response, next: NextFunction) => {
31
- // 处理 Zod 校验错误
32
- if (err instanceof ZodError) {
33
- const fields: Record<string, string> = {};
34
- err.errors.forEach((e) => {
35
- const path = e.path.join(".");
36
- fields[path] = e.message;
37
- });
38
-
39
- return res.status(400).json({
40
- code: 400,
41
- data: null,
42
- msg: "参数校验失败",
43
- error: "ZodValidationError",
44
- fields,
45
- });
46
- }
47
-
48
- // 处理 SQL 错误(给出友好提示)
49
- if (isSQLError(err)) {
50
- logger.error("");
51
- logger.error("❌ 数据库结构可能已变化,请运行以下命令同步:");
52
- logger.error("");
53
- logger.error(" npm run db:migrate");
54
- logger.error("");
55
- }
56
-
57
- // 处理普通错误
58
- const statusCode = err.status || err.statusCode || 500;
59
- if (!isSQLError(err)) {
60
- logger.error("系统错误:", err);
61
- }
62
-
63
- res.status(statusCode).json({
64
- code: statusCode,
65
- data: null,
66
- msg: err.message || "服务器内部错误",
67
- error: logging ? err.stack : "InternalServerError",
68
- fields: {},
69
- });
70
- });
71
- }
@@ -1,35 +0,0 @@
1
- import express, { Express } from "express";
2
- import fs from "fs";
3
- import cookieParser from "cookie-parser";
4
- import cors from "../middleware/Cors";
5
- import ipParser from "../middleware/IpParser";
6
- import uaParser from "../middleware/UaParser";
7
- import { LightUpAppOptions } from "../types/Index";
8
- import logger from "./Logger";
9
-
10
- export default function initExpress(app: Express, options: LightUpAppOptions): void {
11
- app.set("config", options);
12
- app.disable("x-powered-by");
13
- app.use(express.urlencoded({ extended: false, limit: "10mb" }));
14
- app.use(express.json({ limit: "10mb" }));
15
- app.use(cookieParser());
16
- if (options.staticPath) {
17
- if (!fs.existsSync(options.staticPath)) {
18
- fs.mkdirSync(options.staticPath, { recursive: true });
19
- }
20
- app.use(express.static(options.staticPath));
21
- logger.startup("设置", `静态目录: ${options.staticPath}`);
22
- }
23
- if (options.cors) {
24
- app.use(cors);
25
- logger.startup("设置", "跨域请求头");
26
- }
27
- if (options.ip) {
28
- logger.startup("设置", "IP地址解析");
29
- app.use(ipParser);
30
- }
31
- if (options.userAgent) {
32
- logger.startup("设置", "用户代理解析");
33
- app.use(uaParser);
34
- }
35
- }