# Fullstack Toolkits

面向 NestJS 全栈项目的综合工具包，提供开箱即用的开发中间件、工具函数和辅助功能，帮助您快速搭建高效的开发环境。

## 功能特性

本 SDK 导出三大核心模块，让您可以快速集成到项目中：

### 📦 开箱即用的中间件 (Middlewares)
- **Dev Logs 中间件** - 查看和查询应用日志，支持 Trace ID 追踪和路径过滤
- **Collect Logs 中间件** - 收集客户端日志并自动存储到文件
- **OpenAPI 中间件** - 增强 OpenAPI 文档，自动添加源代码引用信息

### 🛠️ 实用工具函数 (Utils)
- **路径规范化** - 自动处理路径格式，确保一致性

### 🔌 通用脚本 (Helpers)
- **Drizzle Schema 后处理** - 自动优化 Drizzle Kit 生成的 schema 文件
  - 中文表名转拼音标识符
  - 自动替换自定义类型（user_profile、file_attachment）
  - 添加系统字段注释（_created_at、_updated_at 等）
  - 优化 import 语句和代码格式
- **Proxy Error Handler** - HTTP 代理错误处理器
  - 显示友好的错误页面
  - 自动读取并展示服务端错误日志
  - 智能提取编译/启动错误信息
  - 支持一键复制和修复建议

## 安装

```bash
npm install @apaas/fullstack-toolkits
# 或
yarn add @apaas/fullstack-toolkits
```

## 快速开始

只需三步，即可为您的项目添加强大的开发工具：

```typescript
import {
  registerMiddlewares,
  createDevLogsMiddleware,
  createCollectLogsMiddleware,
  createOpenapiMiddleware
} from '@apaas/fullstack-toolkits';

// 在 rspack/webpack 或 Vite 开发服务器中注册
registerMiddlewares(devServer.app, [
  createDevLogsMiddleware({ logDir: './logs' }),
  createCollectLogsMiddleware({ logDir: './logs' }),
  createOpenapiMiddleware({ openapiFilePath: './openapi.json' }),
], {
  basePath: '/api',
  isDev: true,
  rootDir: __dirname
});
```

完成！现在您可以：
- 📊 访问 `GET /api/dev/logs/trace/recent` 查看最近的 API 调用
- 📝 通过 `POST /api/dev/logs/collect` 收集客户端日志
- 📚 访问 `GET /api/dev/openapi.json` 获取增强的 API 文档

## 导出内容

### 中间件 (Middlewares)
```typescript
import {
  createDevLogsMiddleware,      // 日志查询中间件
  createCollectLogsMiddleware,  // 日志收集中间件
  createOpenapiMiddleware,      // OpenAPI 增强中间件
} from '@apaas/fullstack-toolkits';
```

### 工具函数 (Utils)
```typescript
import {
  matchesPathPattern,    // 路径模式匹配
  extractPathParams,     // 提取路径参数
  normalizeBasePath,     // 路径规范化
} from '@apaas/fullstack-toolkits';
```

### 辅助函数 (Helpers)
```typescript
import {
  registerMiddlewares,       // 统一注册中间件
  postprocessDrizzleSchema,  // 后处理 Drizzle Schema 文件
  handleDevProxyError,       // HTTP 代理错误处理器
} from '@apaas/fullstack-toolkits';
```

### 兼容工具函数 (Compat)
```typescript
import {
  sendJson,         // 发送 JSON 响应（兼容 Express/Connect）
  sendError,        // 发送错误响应
  sendSuccess,      // 发送成功响应
  getQuery,         // 获取所有查询参数
  getQueryParam,    // 获取单个查询参数
} from '@apaas/fullstack-toolkits';
```

### 类型定义 (Types)
```typescript
import type {
  Middleware,            // 中间件联合类型
  RouteMiddleware,       // 路由中间件类型
  GlobalMiddleware,      // 全局中间件类型
  MiddlewareContext,     // 中间件上下文类型
} from '@apaas/fullstack-toolkits';
```

## 详细使用

### 在不同开发服务器中集成

工具包支持两种类型的中间件：
- **路由中间件**：挂载到特定路径（例如 `/dev/logs`、`/dev/openapi.json`）
- **全局中间件**：应用于所有请求，无需挂载路径

**用于 Rspack / Webpack 开发服务器：**

```typescript
import {
  registerMiddlewares,
  createDevLogsMiddleware,
  createOpenapiMiddleware
} from '@apaas/fullstack-toolkits';

export default {
  devServer: {
    setupMiddlewares: (middlewares, devServer) => {
      if (devServer.app) {
        registerMiddlewares(devServer.app, [
          createDevLogsMiddleware({ logDir: './logs' }),
          createOpenapiMiddleware({
            openapiFilePath: './openapi.json',
            enableEnhancement: true
          }),
        ], {
          basePath: '/api',
          isDev: true,
          rootDir: __dirname
        });
      }
      return middlewares;
    }
  }
}
```

**用于 Vite 开发服务器：**

```typescript
export default {
  plugins: [
    {
      name: 'dev-middlewares',
      configureServer: (server) => {
        registerMiddlewares(server.middlewares, [
          createDevLogsMiddleware({ logDir: './logs' }),
          createOpenapiMiddleware({
            openapiFilePath: './openapi.json',
            enableEnhancement: true
          }),
        ], {
          basePath: '/',
          isDev: true,
          rootDir: __dirname
        });
      }
    }
  ]
}
```

**重要提示：**
- 中间件执行顺序与数组顺序一致
- 全局中间件应放在路由中间件之前
- 路由中间件仅处理匹配其挂载路径的请求

### Vite/Connect 兼容性

本工具包原生支持 Vite 开发服务器。`registerMiddlewares` 会自动检测 Vite/Connect 环境，并添加必要的 Express API 兼容层：

- `res.status()` - 设置响应状态码
- `res.json()` - 发送 JSON 响应
- `res.send()` - 发送响应
- `req.query` - 解析后的查询参数

这意味着您可以直接在 Vite 中使用本工具包，无需额外配置。

**兼容工具函数（可选）：**

如果您正在编写自定义中间件并希望同时支持 Express 和 Vite，可以使用导出的兼容工具函数：

```typescript
import { sendJson, sendError, sendSuccess, getQuery, getQueryParam } from '@lark-apaas/devtool-kits';

// 发送 JSON 响应（自动检测 Express/Connect）
sendJson(res, { data: 'value' }, 200);

// 发送错误响应
sendError(res, 'Not found', error, 404);

// 发送成功响应
sendSuccess(res, { id: 123 });

// 获取查询参数（自动解析 URL 或使用 req.query）
const query = getQuery(req);
const page = getQueryParam(req, 'page');
```

## 中间件详细说明
### Dev Logs 中间件

查看和查询带 Trace ID 支持的应用日志。

**功能特性：**
- 根据 Trace ID 查询日志条目
- 分页浏览最近的追踪调用
- 按 API 路径模式和 HTTP 方法过滤日志
- 支持 OpenAPI/Swagger 路径模式
- 分页读取日志文件

**路由：**
- `GET /dev/logs/app/trace/:traceId` - 根据 Trace ID 获取日志条目
- `GET /dev/logs/trace/recent` - 获取最近的追踪调用（支持分页和过滤）
- `GET /dev/logs/files/:fileName` - 分页获取日志文件内容

**查询参数：**

**用于 `/app/trace/:traceId`：**
- `limit` - 返回条目的最大数量（默认：200，最大：1000）

**用于 `/trace/recent`：**
- `page` - 页码（默认：1）
- `pageSize` - 每页条目数（默认：10，最大：100）
- `path` - 按 API 路径模式过滤（支持通配符）
- `method` - 按 HTTP 方法过滤（GET、POST 等）

**用于 `/files/:fileName`：**
- `page` - 页码（默认：1）
- `pageSize` - 每页行数（默认：200，最大：2000）

**响应示例：**

```typescript
// GET /dev/logs/app/trace/abc123?limit=100
{
  file: "logs/app.log",
  traceId: "abc123",
  count: 5,
  entries: [
    { /* 日志条目 1 */ },
    { /* 日志条目 2 */ }
  ]
}

// GET /dev/logs/trace/recent?page=1&pageSize=10&path=/api/users/*&method=POST
{
  file: "logs/trace.log",
  page: 1,
  pageSize: 10,
  total: 150,
  totalPages: 15,
  path: "/api/users/*",
  method: "POST",
  count: 10,
  calls: [
    { /* 追踪调用 1 */ },
    { /* 追踪调用 2 */ }
  ]
}

// GET /dev/logs/files/app.log?page=1&pageSize=200
{
  file: "logs/app.log",
  page: 1,
  pageSize: 200,
  total: 5000,
  totalPages: 25,
  lines: [
    "日志行 1",
    "日志行 2"
  ]
}
```

**使用示例：**
```typescript
// 注册中间件
createDevLogsMiddleware({
  logDir: './logs',  // 可选，默认为 'logs' 或 context.logDir
})

// 客户端使用示例
// 获取指定 Trace ID 的日志
fetch('/dev/logs/app/trace/abc123?limit=100')
  .then(res => res.json())
  .then(data => console.log(data.entries));

// 按路径和方法过滤获取最近的 API 调用
fetch('/dev/logs/trace/recent?page=1&pageSize=20&path=/api/users/*&method=POST')
  .then(res => res.json())
  .then(data => console.log(data.calls));

// 分页读取日志文件
fetch('/dev/logs/files/app.log?page=1&pageSize=200')
  .then(res => res.json())
  .then(data => console.log(data.lines));
```


### Collect Logs 中间件

从客户端收集日志并存储到日志文件中。

**功能特性：**
- 通过 HTTP POST 收集客户端日志
- 以 JSON Lines 格式存储日志
- 自动创建日志目录
- 可自定义日志文件名

**路由：**
- `POST /dev/logs/collect` - 从客户端收集日志

**请求体：**
```typescript
{
  level: string;        // 日志级别（info、warn、error 等）
  message: string;      // 日志消息
  time: string;         // ISO 时间戳字符串
  source?: string;      // 可选的日志来源标识
  user_id: string;      // 用户 ID
  tenant_id: string;    // 租户 ID
  app_id: string;       // 应用 ID
}
```

**响应：**
```typescript
// 成功
{
  success: true
}

// 错误
{
  message: string;
  error: { name?: string; message: string }
}
```

**使用示例：**
```typescript
// 注册中间件
createCollectLogsMiddleware({
  logDir: './logs',           // 可选，默认为 'logs'
  fileName: 'client.log',     // 可选，默认为 'client.log'
})

// 客户端使用示例
fetch('/dev/logs/collect', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    level: 'error',
    message: 'Something went wrong',
    time: new Date().toISOString(),
    source: 'frontend',
    user_id: 'user123',
    tenant_id: 'tenant456',
    app_id: 'app789'
  })
});
```

**日志文件格式：**
日志以 JSON Lines 格式存储（每行一个 JSON 对象）：
```
{"level":"error","message":"Something went wrong","time":"2025-10-23T10:30:00.000Z","source":"frontend","user_id":"user123","tenant_id":"tenant456","app_id":"app789"}
{"level":"info","message":"User logged in","time":"2025-10-23T10:31:00.000Z","user_id":"user123","tenant_id":"tenant456","app_id":"app789"}
```


### OpenAPI 中间件

提供带源代码引用的增强版 OpenAPI/Swagger 文档。

**功能特性：**
- 自动为 OpenAPI 路径添加控制器源代码位置
- 为每个端点添加文件路径和行号
- 根据上下文转换路径以包含 basePath
- 内置缓存以提升性能
- 支持静态和动态 OpenAPI 规范

**路由：**
- `GET /dev/openapi.json` - 获取增强版 OpenAPI 规范

**响应结构：**
```typescript
{
  openapi: "3.0.0",
  info: { /* OpenAPI 信息 */ },
  paths: {
    "/api/users": {
      get: {
        // ... 原始 OpenAPI 定义
        "x-source": {
          file: "src/controllers/user.controller.ts",
          line: 42,
          method: "getUsers",
          controllerPath: "/users",
          routePath: "/api/users"
        }
      }
    }
  }
}
```

**使用示例：**
```typescript
// 注册中间件
createOpenapiMiddleware({
  openapiFilePath: './openapi.json',     // 必需 - OpenAPI 文件路径
  enableEnhancement: true,                // 可选 - 启用源码信息（默认：true）
  serverDir: './src',                     // 可选 - 服务端目录用于扫描（默认为 context.rootDir）
})

// 客户端使用
fetch('/dev/openapi.json')
  .then(res => res.json())
  .then(spec => {
    // 使用增强后的 OpenAPI 规范
    console.log(spec.paths);

    // 访问源代码信息
    const getUsersSource = spec.paths['/api/users'].get['x-source'];
    console.log(`端点定义在 ${getUsersSource.file}:${getUsersSource.line}`);
  });
```

**增强详情：**

中间件会扫描服务端代码库中的 NestJS 控制器，并为每个端点添加 `x-source` 元数据：
- `file` - 控制器文件的相对路径
- `line` - 定义端点处理器的行号
- `method` - 处理器方法名
- `controllerPath` - 来自 `@Controller()` 装饰器的基础路径
- `routePath` - 包含 basePath 的完整路径

**缓存机制：**
增强后的 OpenAPI 规范基于文件哈希进行缓存。当源 OpenAPI 文件发生变化时，缓存会自动失效。

### 工具函数

#### Drizzle Schema 后处理

自动优化 Drizzle Kit 生成的 PostgreSQL schema 文件，解决常见的代码质量问题。

**主要功能：**
- **中文表名处理** - 自动将中文表名转换为拼音标识符（如：用户表 → yonghu_biao）
- **自定义类型替换** - 将 `unknown()` 类型自动替换为 `userProfile()` 或 `fileAttachment()`
- **系统字段注释** - 为 `_created_at`、`_created_by`、`_updated_at`、`_updated_by` 添加注释
- **Schema 转换** - 移除 `pgSchema` 声明，将 `schema.table()` 转换为 `pgTable()`
- **Import 优化** - 自动添加缺失的导入，移除未使用的导入
- **代码格式化** - 统一换行符、合并多余空行、添加文件头注释

**使用示例：**
```typescript
import { postprocessDrizzleSchema } from '@apaas/fullstack-toolkits';

// 在 Drizzle Kit generate 之后调用
const stats = postprocessDrizzleSchema('./src/db/schema.ts');

if (stats) {
  console.log(`处理完成：`);
  console.log(`- 替换了 ${stats.replacedUnknown} 个未知类型`);
  console.log(`- 未匹配的自定义类型: ${stats.unmatchedUnknown.length}`);
}
```

**配合 Drizzle Kit 使用：**
```json
// package.json
{
  "scripts": {
    "db:generate": "drizzle-kit generate && node -e \"require('@apaas/fullstack-toolkits').postprocessDrizzleSchema('./src/db/schema.ts')\""
  }
}
```

**处理前后对比：**
```typescript
// 处理前
export const 用户表 = workspace_xxx.table("user_table", {
  // TODO: failed to parse database type 'user_profile'
  profile: unknown("profile"),
  _created_at: timestamp("_created_at"),
});

// 处理后
/** auto generated, do not edit */
import { pgTable, timestamp } from "drizzle-orm/pg-core"
import { userProfile } from "./types"

export const yonghu_biao = pgTable("user_table", {
  profile: userProfile("profile"),
  // System field: Creation time (auto-filled, do not modify)
  _created_at: timestamp("_created_at"),
});
```

#### 路径匹配

强大的路径模式匹配功能，适用于 OpenAPI/Swagger/NestJS 路由。

**支持的模式：**
- 精确匹配：`/api/users` === `/api/users`
- 路径参数：`/api/users/{id}` 匹配 `/api/users/123`
- 单层通配符：`/api/*/users` 匹配 `/api/v1/users`
- 递归通配符：`/files/**` 匹配 `/files/a/b/c`
- 前缀匹配：`/api/users` 匹配 `/api/users/123`

**使用示例：**
```typescript
import { matchesPathPattern, extractPathParams } from '@apaas/fullstack-toolkits';

// 匹配路径模式
matchesPathPattern('/api/users/123', '/api/users/{id}'); // true
matchesPathPattern('/api/v1/users', '/api/*/users'); // true

// 提取路径参数
extractPathParams('/api/users/123', '/api/users/{id}');
// 返回：{ id: '123' }

extractPathParams('/api/users/123/posts/456', '/api/users/{userId}/posts/{postId}');
// 返回：{ userId: '123', postId: '456' }
```

#### 路径规范化

```typescript
import { normalizeBasePath } from '@apaas/fullstack-toolkits';

// 规范化基础路径（添加前导斜杠，移除尾部斜杠）
normalizeBasePath('api'); // '/api'
normalizeBasePath('/api/'); // '/api'
normalizeBasePath('api/v1'); // '/api/v1'
```

## 许可证

MIT

