@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
package/core/app.ts DELETED
@@ -1,200 +0,0 @@
1
- import { Express } from "express";
2
- import { createState } from "./state";
3
- import { setBaseDir, scan, addScanPath } from "../feature/scanner/FileScanner";
4
- import { MindBaseAppOptions } from "../types/Index";
5
- import initExpress from "../utils/InitExpress";
6
- import { setupDatabase, handleDatabaseMigration } from "../utils/InitDatabase";
7
- import { checkAndMigrateDatabase } from "../utils/DatabaseMigration";
8
- import { registerComponents } from "../utils/ComponentRegistry";
9
- import { setupErrorHandlers } from "../utils/InitErrorHandler";
10
- import { handleSchemaSync } from "../utils/SchemaSync";
11
- import { handleSchemaMigrate } from "../utils/SchemaMigrate";
12
- import { startServer } from "../utils/HttpServer";
13
- import logger from "../utils/Logger";
14
- import { initDocManager } from "../utils/DocManager";
15
-
16
- /**
17
- * MindBase 应用实例
18
- */
19
- export interface MindBaseApp {
20
- /** 注册插件模块 */
21
- use(plugin: any): void;
22
- /** 启动应用(初始化数据库、注册路由、启动 HTTP 服务) */
23
- startup(): Promise<void>;
24
- /** 获取 Express 实例 */
25
- getApp(): Express;
26
- /** 获取 Drizzle 数据库实例 */
27
- getDB(): any;
28
- /** 获取应用配置选项 */
29
- getOptions(): MindBaseAppOptions;
30
- }
31
-
32
- /**
33
- * 创建 MindBase 应用实例
34
- * @param options 应用配置选项
35
- * @returns MindBase 应用实例
36
- * @example
37
- * const app = createApp({
38
- * port: 3000,
39
- * logLevel: "debug",
40
- * });
41
- * app.use(auth);
42
- * await app.startup();
43
- */
44
- export function createApp(options: MindBaseAppOptions = {}): MindBaseApp {
45
- logger.startup("初始化", "应用初始化……");
46
- const stateInstance = createState(options);
47
- const plugins: any[] = [];
48
-
49
- // 初始化日志级别
50
- if (stateInstance.options.logLevel) {
51
- logger.setLevel(stateInstance.options.logLevel);
52
- }
53
-
54
- const app: MindBaseApp = {
55
- use: async (plugin) => {
56
- if (typeof plugin.install === "function") {
57
- plugins.push(plugin);
58
- }
59
- if (typeof plugin.__modulePath === "string") {
60
- logger.startup("模块", `注册:${plugin.__modulePath}`);
61
- addScanPath(plugin.__modulePath);
62
- }
63
- },
64
- startup: async () => {
65
- // migrate 模式:收集 schema + 执行 push(一体化迁移)
66
- if (stateInstance.options.mode === "migrate") {
67
- await handleSchemaMigrate();
68
- return;
69
- }
70
-
71
- // sync 模式:仅收集 schema 路径(兼容旧版本)
72
- if (stateInstance.options.mode === "sync") {
73
- await handleSchemaSync();
74
- return;
75
- }
76
-
77
- // 0. 初始化 Express 基础配置
78
- initExpress(stateInstance.express, stateInstance.options);
79
-
80
- // 1. 准备扫描环境
81
- await prepareScanEnvironment();
82
-
83
- // 2. 扫描组件
84
- const scannedResults = await scanComponents();
85
-
86
- // 3. 初始化数据存储
87
- await initializeDataStorage(scannedResults);
88
-
89
- // 4. 执行插件安装 (此时 DB 已就绪)
90
- await installPlugins();
91
-
92
- // 5. 注册组件
93
- registerComponents(stateInstance, scannedResults);
94
-
95
- // 6. 注册错误处理
96
- setupErrorHandlers(stateInstance.express, stateInstance.options.logging);
97
-
98
- // 7. 启动 HTTP 服务
99
- await startHttpServer();
100
-
101
- logger.startup("初始化", "✅ 应用启动完成");
102
- },
103
-
104
- getApp: () => stateInstance.express,
105
- getDB: () => stateInstance.db,
106
- getOptions: () => stateInstance.options,
107
- };
108
-
109
- // 准备扫描环境
110
- async function prepareScanEnvironment() {
111
- setBaseDir(process.cwd());
112
- logger.startup("扫描", `基准目录: ${process.cwd()}`);
113
- }
114
-
115
- // 扫描组件
116
- async function scanComponents() {
117
- const scannedResults = await scan();
118
-
119
- // 统计缓存命中情况
120
- const routeFiles = scannedResults.filter(r => r.type === "route");
121
- const cacheHits = routeFiles.filter(r => r.cacheHit).length;
122
- const total = routeFiles.length;
123
-
124
- if (cacheHits > 0) {
125
- logger.startup("扫描", `发现 ${scannedResults.length} 个组件文件 (缓存命中: ${cacheHits}/${total})`);
126
- } else {
127
- logger.startup("扫描", `发现 ${scannedResults.length} 个组件文件`);
128
- }
129
-
130
- return scannedResults;
131
- }
132
-
133
- // 初始化数据存储
134
- async function initializeDataStorage(scannedResults) {
135
- // 初始化数据库与 Schema
136
- setupDatabase(stateInstance, scannedResults);
137
- stateInstance.express.set("db", stateInstance.db);
138
-
139
- // 收集数据库信息
140
- if (stateInstance.db) {
141
- const dbPath = stateInstance.options.database?.path || "./data/app.db";
142
- logger.startup("数据库", `已连接: SQLite (${dbPath})`);
143
- }
144
-
145
- // 数据库迁移已改为独立命令(npm run db:migrate)
146
- // 不再在启动时自动执行,以提升启动速度
147
- // await handleDatabaseMigrationStep();
148
-
149
- // 初始化文档管理器
150
- const docDbPath = stateInstance.options.database?.path ? stateInstance.options.database.path.replace(/app\.db$/, "doc.db") : "./data/doc.db";
151
- initDocManager({ path: docDbPath });
152
- logger.startup("数据库", `文档库已初始化: ${docDbPath}`);
153
- }
154
-
155
- // 处理数据库迁移
156
- async function handleDatabaseMigrationStep() {
157
- if (stateInstance.options.autoMigrate !== false) {
158
- try {
159
- await checkAndMigrateDatabase(stateInstance);
160
- // 已在 DatabaseMigration 中打印日志,无需额外处理
161
- } catch (error) {
162
- logger.error("❌ 数据库结构检查失败:", error);
163
- }
164
- } else {
165
- // 只提醒,不执行迁移
166
- try {
167
- await handleDatabaseMigration(stateInstance.db, stateInstance.schemas);
168
- } catch (error) {
169
- logger.error("❌ 数据库结构检查失败:", error);
170
- }
171
- }
172
- }
173
-
174
- // 执行插件安装
175
- async function installPlugins() {
176
- for (const plugin of plugins) {
177
- await plugin.install(app);
178
- }
179
- }
180
-
181
- // 启动 HTTP 服务
182
- async function startHttpServer() {
183
- const listenPort = stateInstance.options.port || 3000;
184
- await startServer(stateInstance.express, listenPort);
185
-
186
- // 打印服务器启动信息
187
- const serverUrl = `http://localhost:${listenPort}`;
188
- logger.startup("服务", `监听端口: ${listenPort}`);
189
- logger.startup("服务", `访问地址: ${serverUrl}`);
190
- }
191
-
192
- return app;
193
- }
194
-
195
- // 扩展 Express Application 类型,添加 getDB 方法
196
- declare module "express" {
197
- export interface Application {
198
- getDB(): any;
199
- }
200
- }
@@ -1,38 +0,0 @@
1
- import { findPackageRoot } from "./FindPackageRoot";
2
- import { getModulePath } from "./GetModulePath";
3
- import { LightUpApp } from "../../types";
4
- import logger from "../../utils/Logger";
5
-
6
- export interface LightUpModule {
7
- install(app: LightUpApp): Promise<void>;
8
- __modulePath: string;
9
- }
10
-
11
- /**
12
- * 创建一个 LightUp 模块
13
- * @param install 模块安装函数
14
- * @param callerPath 调用者的文件路径(通常是 __filename),如果不提供则尝试通过调用栈获取
15
- * @returns LightUp 模块实例
16
- * @throws 如果模块创建失败
17
- */
18
- export function createModule(install, callerPath?: string): LightUpModule {
19
- if (typeof install !== "function") {
20
- throw new Error("模块安装函数必须是一个函数");
21
- }
22
-
23
- try {
24
- // 优先使用传入的 callerPath,否则通过调用栈获取
25
- const modulePath = callerPath || getModulePath();
26
- const packageRoot = findPackageRoot(modulePath);
27
-
28
- logger.debug(`创建模块:路径=${modulePath},包根目录=${packageRoot}`);
29
-
30
- return {
31
- install,
32
- __modulePath: packageRoot,
33
- };
34
- } catch (error) {
35
- logger.error("模块创建失败:", error);
36
- throw new Error(`模块创建失败:${error instanceof Error ? error.message : String(error)}`);
37
- }
38
- }
@@ -1,58 +0,0 @@
1
- import path from "path";
2
- import fs from "fs";
3
-
4
- /**
5
- * 查找包含 package.json 的根目录
6
- * @param startPath 开始查找的路径
7
- * @returns 包含 package.json 的目录路径
8
- * @throws 如果输入路径无效
9
- */
10
- export function findPackageRoot(startPath: string): string {
11
- // 验证输入路径
12
- if (!startPath || typeof startPath !== "string") {
13
- throw new Error("无效的起始路径:路径必须是非空字符串");
14
- }
15
-
16
- // 确保路径是绝对路径
17
- let currentPath: string;
18
- try {
19
- currentPath = path.isAbsolute(startPath) ? startPath : path.resolve(startPath);
20
- } catch (e) {
21
- throw new Error(`无法解析路径:${startPath}`);
22
- }
23
-
24
- // 验证路径是否存在
25
- try {
26
- if (!fs.existsSync(currentPath)) {
27
- throw new Error(`路径不存在:${currentPath}`);
28
- }
29
-
30
- // 如果是文件,从其父目录开始查找
31
- const stat = fs.statSync(currentPath);
32
- if (stat.isFile()) {
33
- currentPath = path.dirname(currentPath);
34
- }
35
- } catch (e) {
36
- throw new Error(`路径验证失败:${e instanceof Error ? e.message : String(e)}`);
37
- }
38
-
39
- // 向上查找 package.json
40
- while (currentPath !== path.parse(currentPath).root) {
41
- try {
42
- const pkgJsonPath = path.join(currentPath, "package.json");
43
- if (fs.existsSync(pkgJsonPath)) {
44
- // 验证找到的 package.json 是否是文件
45
- const pkgStat = fs.statSync(pkgJsonPath);
46
- if (pkgStat.isFile()) {
47
- return currentPath;
48
- }
49
- }
50
- } catch (e) {
51
- // 忽略单个路径的访问错误,继续向上查找
52
- }
53
- currentPath = path.dirname(currentPath);
54
- }
55
-
56
- // 如果没有找到 package.json,返回起始路径的父目录
57
- return path.dirname(startPath);
58
- }
@@ -1,58 +0,0 @@
1
- import * as path from "path";
2
-
3
- /**
4
- * 获取当前模块的路径
5
- * @returns 当前模块的绝对路径
6
- * @throws 如果无法确定模块路径
7
- */
8
- export function getModulePath(): string {
9
- // 从调用栈中提取调用者的路径
10
- try {
11
- const error = new Error();
12
- if (error.stack) {
13
- console.log("DEBUG STACK:", error.stack);
14
- const stackLines = error.stack.split("\n");
15
-
16
- // 跳过内部调用,找到真正的调用者
17
- // 需要跳过:getModulePath, createModule, ts-node, node:internal
18
- for (const line of stackLines) {
19
- // 跳过内部函数和 Node.js/ts-node 的调用
20
- if (!line.includes("at") ||
21
- line.includes("getModulePath") ||
22
- line.includes("createModule") ||
23
- line.includes("CreateModule.ts") ||
24
- line.includes("ts-node") ||
25
- line.includes("node:internal")) {
26
- continue;
27
- }
28
-
29
- // 提取文件路径
30
- // 格式可能是:at xxx (/path/to/file.ts:1:2) 或 at /path/to/file.ts:1:2
31
- const match = line.match(/\(([^:]+\.ts)/) || line.match(/at\s+([^:]+\.ts)/);
32
- if (match && match[1]) {
33
- const filePath = match[1];
34
- console.log("提取到的路径:", filePath);
35
- return path.resolve(filePath);
36
- }
37
- }
38
- }
39
- } catch (e) {
40
- // 忽略提取 stack 时的错误
41
- }
42
-
43
- throw new Error("无法确定模块路径:当前环境不支持 __filename 且无法从调用栈中提取路径");
44
- }
45
-
46
- /**
47
- * 获取当前模块所在的目录
48
- * @returns 当前模块的目录路径
49
- * @throws 如果无法确定模块路径
50
- */
51
- export function getModuleDir(): string {
52
- try {
53
- const modulePath = getModulePath();
54
- return path.dirname(modulePath);
55
- } catch (error) {
56
- throw new Error(`无法获取模块目录:${error instanceof Error ? error.message : String(error)}`);
57
- }
58
- }
package/core/state.ts DELETED
@@ -1,72 +0,0 @@
1
- import express, { Express } from "express";
2
- import { LogLevel } from "../types";
3
-
4
- export interface MindBaseAppOptions {
5
- /**
6
- * 启动模式:
7
- * - normal: 正常启动服务
8
- * - sync: 同步数据库 Schema(仅收集)
9
- * - migrate: 执行数据库迁移
10
- * @default "normal"
11
- */
12
- mode?: "normal" | "sync" | "migrate";
13
- /** 服务监听端口 @default 3000 */
14
- port?: number;
15
- /** 是否启用请求日志 @default true */
16
- logging?: boolean;
17
- /** 静态文件目录路径 */
18
- staticPath?: string;
19
- /** 是否解析 User-Agent 信息 @default true */
20
- userAgent?: boolean;
21
- /** 是否解析 IP 地理位置信息 @default true */
22
- ip?: boolean;
23
- /** 是否启用 CORS 跨域 @default true */
24
- cors?: boolean;
25
- /** API 路由前缀,如 "/api" */
26
- apiPrefix?: string;
27
- /**
28
- * 日志级别(优先级从低到高):
29
- * - debug: 调试信息(显示所有)
30
- * - info: 普通信息(隐藏 debug)
31
- * - warn: 警告信息(隐藏 debug、info)
32
- * - error: 错误信息(隐藏 debug、info、warn)
33
- * - silent: 静默模式(隐藏所有)
34
- * @default "info"
35
- */
36
- logLevel?: LogLevel;
37
- /** 是否自动迁移数据库表结构 @default false */
38
- autoMigrate?: boolean;
39
- /** 数据库配置 */
40
- database?: {
41
- /** SQLite 数据库文件路径 @default "./data/app.db" */
42
- path?: string;
43
- };
44
- }
45
- export interface AppState {
46
- options: MindBaseAppOptions;
47
- express: Express;
48
- db?: any; // Drizzle 数据库实例
49
- schemas?: Record<string, any>; // 合并后的所有 schema
50
- }
51
-
52
- export function createState(options: MindBaseAppOptions = {}): AppState {
53
- return {
54
- options: {
55
- mode: options.mode || (process.env.MIND_BASE_MODE as any) || "normal",
56
- port: options.port || 3000,
57
- logging: options.logging !== false,
58
- staticPath: options.staticPath,
59
- userAgent: options.userAgent !== false,
60
- ip: options.ip !== false,
61
- cors: options.cors !== false,
62
- apiPrefix: options.apiPrefix,
63
- logLevel: options.logLevel || "info",
64
- autoMigrate: options.autoMigrate ?? false,
65
- database: {
66
- path: options.database?.path || "./data/app.db",
67
- },
68
- },
69
- express: express(),
70
- schemas: {},
71
- };
72
- }
@@ -1,63 +0,0 @@
1
- import { CronJob } from "cron";
2
- import { randomUUID } from "crypto";
3
-
4
- interface CronConfig {
5
- timezone?: string;
6
- }
7
-
8
- interface CronJobMap {
9
- [id: string]: CronJob;
10
- }
11
-
12
- let jobs: CronJobMap = {};
13
- let config: CronConfig = {
14
- timezone: "Asia/Shanghai",
15
- };
16
-
17
- export function setConfig(newConfig: CronConfig): void {
18
- config = {
19
- ...config,
20
- ...newConfig,
21
- };
22
- }
23
-
24
- export function addCron(pattern: string, handler: () => void | Promise<void>, customId?: string): string {
25
- const id = customId || generateId();
26
- if (jobs[id]) {
27
- jobs[id].stop();
28
- }
29
- const job = new CronJob(pattern, handler, null, true, config.timezone);
30
- jobs[id] = job;
31
- return id;
32
- }
33
-
34
- export function stopCron(id: string): boolean {
35
- const job = jobs[id];
36
- if (job) {
37
- job.stop();
38
- delete jobs[id];
39
- return true;
40
- }
41
- return false;
42
- }
43
-
44
- export function stopAllCron(): void {
45
- Object.values(jobs).forEach((job) => {
46
- job.stop();
47
- });
48
- jobs = {};
49
- }
50
-
51
- function generateId(): string {
52
- return randomUUID();
53
- }
54
-
55
- const CronManager = {
56
- setConfig,
57
- addCron,
58
- stopCron,
59
- stopAllCron,
60
- };
61
-
62
- export default CronManager;
63
- export type { CronConfig };