kb-server 0.0.1-beta.2 → 0.0.1-beta.20

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
@@ -2,4 +2,19 @@
2
2
 
3
3
  快速创建一个 Node 服务
4
4
 
5
- 快速创建API、标准错误码、Server、支持统一鉴权函数(API级别)
5
+ 快速创建 API、标准错误码、Server、支持统一鉴权函数(API 级别)、自定义中间件、日志、支持SSE
6
+
7
+ ```javascript
8
+ import { createServer } from "kb-server";
9
+ import * as apis from "./apis";
10
+
11
+ // 写法一
12
+ const server = createServer({ apis });
13
+ server.listen(3000);
14
+
15
+ // 写法二
16
+ (async () => {
17
+ // 其他的异步操作,例如:初始化数据库
18
+ return createServer({ apis });
19
+ })().then((app) => app.listen(3000));
20
+ ```
@@ -23,9 +23,10 @@ export interface APIErrorResponse {
23
23
  RequestId: string;
24
24
  };
25
25
  }
26
- export type AuthFunction = (action: string, req: express.Request) => Promise<Record<string, any> | false>;
26
+ export type AuthFunction = (action: string, req: express.Request) => Promise<Record<string, any> | boolean> | boolean | Record<string, any>;
27
27
  interface IPackAPIOptions {
28
28
  authFn?: AuthFunction;
29
+ log?: boolean;
29
30
  }
30
31
  /**
31
32
  * API包装函数
@@ -3,13 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.packAPI = void 0;
4
4
  const create_errors_1 = require("./create-errors");
5
5
  const uuid_1 = require("uuid");
6
+ const logger_1 = require("../helper/logger");
6
7
  /**
7
8
  * API包装函数
8
9
  * @param apis API列表
9
10
  * @returns
10
11
  */
11
12
  const packAPI = (apis, options) => {
12
- const { authFn } = options || {};
13
+ const { authFn, log = true } = options || {};
13
14
  return async (req, res) => {
14
15
  // 生成API映射
15
16
  const apiMap = new Map(Object.entries(apis).map(([action, execution]) => [action, execution]));
@@ -25,6 +26,9 @@ const packAPI = (apis, options) => {
25
26
  try {
26
27
  // API 解析
27
28
  const { Action, ...params } = req.body || {};
29
+ if (log) {
30
+ logger_1.logger.log("info", `请求入参:${JSON.stringify(req.body)} - RequestId: ${requestId}`);
31
+ }
28
32
  // 接口未定义
29
33
  if (!Action) {
30
34
  throw new create_errors_1.CommonErrors.InvalidParameter.EmptyAPIRequest();
@@ -38,8 +42,10 @@ const packAPI = (apis, options) => {
38
42
  if (!authResult) {
39
43
  throw new create_errors_1.CommonErrors.FailOperation.NoPermission();
40
44
  }
41
- // 把权限信息写入上下文
42
- ctx.AuthInfo = authResult || {};
45
+ if (typeof authResult !== "boolean") {
46
+ // 把权限信息写入上下文
47
+ ctx.AuthInfo = authResult || {};
48
+ }
43
49
  }
44
50
  // API 处理
45
51
  const execution = apiMap.get(Action);
@@ -57,11 +63,12 @@ const packAPI = (apis, options) => {
57
63
  };
58
64
  // 完成响应
59
65
  took = Date.now() - start;
60
- console.log("响应:", took, JSON.stringify(response));
66
+ logger_1.logger.log("info", `响应:${JSON.stringify(response)}`);
67
+ logger_1.logger.log("info", `耗时:${took} ms - RequestId: ${requestId}`);
61
68
  return res.send(response);
62
69
  }
63
70
  catch (rawError) {
64
- console.log("原始错误:", rawError);
71
+ logger_1.logger.log("error", rawError);
65
72
  // 未知错误
66
73
  let error = new create_errors_1.CommonErrors.InternalError.UnknownError();
67
74
  // 可控错误
@@ -83,7 +90,8 @@ const packAPI = (apis, options) => {
83
90
  };
84
91
  // 完成响应
85
92
  took = Date.now() - start;
86
- console.log("响应:", took, JSON.stringify(response));
93
+ logger_1.logger.log("info", `响应:${JSON.stringify(response)}`);
94
+ logger_1.logger.log("info", `耗时:${took} ms - RequestId: ${requestId}`);
87
95
  return res.send(response);
88
96
  }
89
97
  };
@@ -0,0 +1 @@
1
+ export declare const callService: <P = Record<string, any>, R = any>(endpoint: string, body: P) => Promise<R>;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.callService = void 0;
7
+ const node_fetch_1 = __importDefault(require("node-fetch"));
8
+ const create_errors_1 = require("./create-errors");
9
+ const callService = async (endpoint, body) => {
10
+ const response = await (0, node_fetch_1.default)(endpoint, {
11
+ headers: {
12
+ "Content-Type": "application/json",
13
+ },
14
+ method: "POST",
15
+ body: JSON.stringify(body),
16
+ });
17
+ const result = await response.json();
18
+ if (result?.Response?.Error) {
19
+ const errMsg = result.Response.Error?.Message;
20
+ if (errMsg) {
21
+ throw new create_errors_1.CommonErrors.InternalError.ServiceError(errMsg);
22
+ }
23
+ throw new create_errors_1.CommonErrors.InternalError.ServiceError();
24
+ }
25
+ return result?.Response?.Data;
26
+ };
27
+ exports.callService = callService;
@@ -1,15 +1,24 @@
1
+ import { ValidationError } from "class-validator";
1
2
  import { Class } from "utility-types";
2
- export interface ServerContext {
3
+ export interface ServerContext<A = any> {
3
4
  /** 被调用方的 RequestId */
4
5
  RequestId: string;
5
6
  /** 权限信息 */
6
- AuthInfo?: Record<string, any>;
7
+ AuthInfo?: A;
7
8
  }
8
9
  export type API<P, R> = (param: P) => Promise<R>;
9
- export type APIExecution<P, R> = (params: P, ctx: ServerContext) => Promise<R>;
10
- export type AnyAPIExecution = APIExecution<any, any>;
10
+ export type APIExecution<P, R, A> = (
11
+ /** 请求入参 */
12
+ params: P,
13
+ /** 请求上下文 */
14
+ ctx: ServerContext<A>) => Promise<R>;
15
+ export type AnyAPIExecution = APIExecution<any, any, any>;
11
16
  export type APIs = Record<string, AnyAPIExecution>;
12
- export declare function createAPI<P, R>(ParamsClass: Class<P>, execution: APIExecution<P, R>): API<P, R>;
17
+ export declare function createAPI<P, R, A>(ParamsClass: Class<P>, execution: APIExecution<P, R, A>): API<P, R>;
13
18
  type StubParam<T> = T extends (params: infer P) => any ? P : never;
14
- export declare function implementAPI<A extends (params: any) => any>(_actionStub: A, ParamsClass: Class<StubParam<A>>, execution: APIExecution<StubParam<A>, ReturnType<A>>): API<StubParam<A>, ReturnType<A>>;
19
+ export declare function implementAPI<F extends (params: any) => any, A = Record<string, any>>(_actionStub: F, ParamsClass: Class<StubParam<F>>, execution: APIExecution<StubParam<F>, ReturnType<F>, A>): API<StubParam<F>, ReturnType<F>>;
20
+ /**
21
+ * 解析错误信息
22
+ */
23
+ export declare function getMessage(message: ValidationError, propertyPath?: string): string[];
15
24
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.implementAPI = exports.createAPI = void 0;
3
+ exports.getMessage = exports.implementAPI = exports.createAPI = void 0;
4
4
  const class_transformer_1 = require("class-transformer");
5
5
  const class_validator_1 = require("class-validator");
6
6
  const create_errors_1 = require("./create-errors");
@@ -15,10 +15,8 @@ function createAPI(ParamsClass, execution) {
15
15
  : (0, class_transformer_1.plainToClass)(ParamsClass, params);
16
16
  const errors = await (0, class_validator_1.validate)(paramsToCheck || {});
17
17
  if (errors?.length) {
18
- const messages = errors.map((x, index) => {
19
- return `\n${index}.<${x.property}>:${Object.values(x.constraints || { unknown: "reason unknown" }).join(", ")}`;
20
- });
21
- throw new create_errors_1.CommonErrors.InvalidParameter.ValidationError(messages.join(" "));
18
+ const errorMessages = errors.flatMap((error) => getMessage(error));
19
+ throw new create_errors_1.CommonErrors.InvalidParameter.ValidationError(errorMessages.join("\n"));
22
20
  }
23
21
  // 执行函数
24
22
  return await execution(params, ctx);
@@ -30,3 +28,26 @@ function implementAPI(_actionStub, ParamsClass, execution) {
30
28
  return createAPI(ParamsClass, execution);
31
29
  }
32
30
  exports.implementAPI = implementAPI;
31
+ /**
32
+ * 解析错误信息
33
+ */
34
+ function getMessage(message, propertyPath = "") {
35
+ const messages = [];
36
+ // 构建当前属性的完整路径
37
+ const currentPropertyPath = propertyPath
38
+ ? `${propertyPath}.${message.property}`
39
+ : message.property;
40
+ if (message.constraints) {
41
+ // 将所有约束错误合并为一个字符串
42
+ const constraintErrors = Object.values(message.constraints).join(", ");
43
+ messages.push(`${currentPropertyPath}: ${constraintErrors}`);
44
+ }
45
+ if (message.children && message.children.length > 0) {
46
+ // 递归处理子错误,并传递更新后的属性路径
47
+ for (const [_index, child] of message.children.entries()) {
48
+ messages.push(...getMessage(child, currentPropertyPath));
49
+ }
50
+ }
51
+ return messages;
52
+ }
53
+ exports.getMessage = getMessage;
@@ -1,8 +1,54 @@
1
+ import express from "express";
1
2
  import { AuthFunction } from "./api-middleware";
2
3
  import { APIs } from "./create-api";
4
+ import { SSEHandlers } from "./sse-middleware";
3
5
  export interface ICreateServerParams {
6
+ /**
7
+ * API列表
8
+ */
4
9
  apis: APIs;
10
+ /**
11
+ * SSE配置
12
+ */
13
+ sse?: {
14
+ handlers: SSEHandlers;
15
+ route?: string;
16
+ };
17
+ /**
18
+ * 鉴权函数
19
+ *
20
+ * 可以通过返回布尔值来定义权限,也可以直接返回数据对象,数据对象会被传递到上下文 ctx 中(AuthInfo)
21
+ */
5
22
  authFn?: AuthFunction;
23
+ /**
24
+ * 默认请求日志,true开启,false关闭
25
+ */
26
+ log?: boolean;
27
+ /**
28
+ * 自定义中间件列表
29
+ *
30
+ * @example
31
+ *
32
+ * ```
33
+ * import cors from "cors"; // 引入cors中间件
34
+ *
35
+ * ...其他代码...
36
+ *
37
+ * const server = createServer(
38
+ * {
39
+ * apis: myAPIs,
40
+ * authFn: myAuthFunction,
41
+ * log: true,
42
+ * middlewares: [cors()], // 传入cors中间件
43
+ * },
44
+ * {
45
+ * limit: "5mb",
46
+ * }
47
+ *);
48
+ * ```
49
+ *
50
+ */
51
+ middlewares?: express.RequestHandler[];
6
52
  }
7
53
  export interface ICreateServerOptions {
8
54
  limit?: string | number | undefined;
@@ -26,18 +26,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.createServer = void 0;
27
27
  const express_1 = __importStar(require("express"));
28
28
  const api_middleware_1 = require("./api-middleware");
29
+ const sse_middleware_1 = require("./sse-middleware");
29
30
  /**
30
31
  * 创建 Server
31
32
  */
32
33
  function createServer(params, options) {
33
- const { apis, authFn } = params || {};
34
+ const { apis, sse, authFn, log, middlewares = [] } = params || {};
34
35
  const { limit = "10mb" } = options || {};
36
+ const { handlers, route } = sse || {};
35
37
  const app = (0, express_1.default)();
36
38
  // POST 参数获取
37
39
  app.use((0, express_1.urlencoded)({ extended: true, limit }));
38
40
  app.use((0, express_1.json)({ limit }));
41
+ // 使用自定义中间件
42
+ middlewares.forEach((middleware) => app.use(middleware));
43
+ // 注入SSE
44
+ handlers && app.use((0, sse_middleware_1.packSSE)(handlers, { route }));
39
45
  // 注入API
40
- app.use((0, api_middleware_1.packAPI)(apis, { authFn }));
46
+ app.use((0, api_middleware_1.packAPI)(apis, { authFn, log }));
41
47
  return app;
42
48
  }
43
49
  exports.createServer = createServer;
@@ -0,0 +1,25 @@
1
+ import express from "express";
2
+ import { ServerContext } from "./create-api";
3
+ import { AuthFunction } from "./api-middleware";
4
+ export type SseExecution<P, R, A> = (
5
+ /** 请求入参 */
6
+ params: P,
7
+ /** 请求上下文 */
8
+ ctx: ServerContext<A>,
9
+ /**
10
+ * 允许执行的操作
11
+ */
12
+ operations: {
13
+ /** 发送消息 */
14
+ send: (event: string, message: string) => void;
15
+ }) => Promise<R>;
16
+ export type AnySseExecution = SseExecution<any, any, any>;
17
+ export type SSEHandlers = Record<string, AnySseExecution>;
18
+ interface IPackSSEOptions {
19
+ authFn?: AuthFunction;
20
+ route?: string;
21
+ log?: boolean;
22
+ }
23
+ export declare const packSSE: (sseHandlers: SSEHandlers, options?: IPackSSEOptions) => express.RequestHandler;
24
+ export declare const createSSEMessage: (event: string, message: string) => string;
25
+ export {};
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSSEMessage = exports.packSSE = void 0;
4
+ const uuid_1 = require("uuid");
5
+ const logger_1 = require("../helper/logger");
6
+ const create_errors_1 = require("./create-errors");
7
+ const short_id_1 = require("../helper/short-id");
8
+ const packSSE = (sseHandlers, options) => {
9
+ const sseMap = new Map(Object.entries(sseHandlers).map(([action, execution]) => [
10
+ action,
11
+ execution,
12
+ ]));
13
+ const { authFn, log = true, route = "/sse" } = options || {};
14
+ return async (req, res, next) => {
15
+ const { path } = req;
16
+ // 路径不是SSE的路径
17
+ if (path !== route) {
18
+ next();
19
+ return;
20
+ }
21
+ // 请求开始时间
22
+ const start = Date.now();
23
+ let took = 0;
24
+ // 生成请求ID
25
+ const requestId = (0, uuid_1.v4)();
26
+ // 上下文
27
+ const ctx = {
28
+ RequestId: requestId,
29
+ };
30
+ try {
31
+ // API 解析
32
+ const { Action, ...params } = req.body || {};
33
+ if (log) {
34
+ logger_1.logger.log("info", `请求入参:${JSON.stringify(req.body)} - RequestId: ${requestId}`);
35
+ }
36
+ // 接口未定义
37
+ if (!Action) {
38
+ throw new create_errors_1.CommonErrors.InvalidParameter.EmptyAPIRequest();
39
+ }
40
+ // 处理鉴权函数
41
+ if (authFn !== null && authFn !== undefined) {
42
+ if (typeof authFn !== "function") {
43
+ throw new create_errors_1.CommonErrors.ResourceNotFound.AuthFunctionNotFound();
44
+ }
45
+ const authResult = await authFn(Action, req);
46
+ if (!authResult) {
47
+ throw new create_errors_1.CommonErrors.FailOperation.NoPermission();
48
+ }
49
+ if (typeof authResult !== "boolean") {
50
+ // 把权限信息写入上下文
51
+ ctx.AuthInfo = authResult || {};
52
+ }
53
+ }
54
+ // API 处理
55
+ const execution = sseMap.get(Action);
56
+ if (typeof execution !== "function") {
57
+ throw new create_errors_1.CommonErrors.ResourceNotFound.APINotFound();
58
+ }
59
+ /**
60
+ * 开始处理SSE请求就要统计
61
+ *
62
+ * 请求处理完毕的时间
63
+ *
64
+ * 不要让 API 随意改写 req res 里面的东西
65
+ *
66
+ * 统一管理请求头
67
+ *
68
+ */
69
+ // 处理SSE
70
+ res.writeHead(200, {
71
+ "Content-Type": "text/event-stream",
72
+ "Cache-Control": "no-cache",
73
+ Connection: "keep-alive",
74
+ });
75
+ const send = (event, message) => {
76
+ const response = (0, exports.createSSEMessage)(event, message);
77
+ res.write(response);
78
+ logger_1.logger.log("info", `发送消息:${response}`);
79
+ };
80
+ await execution(params, ctx, { send });
81
+ // 完成响应
82
+ took = Date.now() - start;
83
+ logger_1.logger.log("info", `耗时:${took} ms - RequestId: ${requestId}`);
84
+ return res.end();
85
+ }
86
+ catch (rawError) {
87
+ logger_1.logger.log("error", rawError);
88
+ // 未知错误
89
+ let error = new create_errors_1.CommonErrors.InternalError.UnknownError();
90
+ // 可控错误
91
+ if (rawError instanceof create_errors_1.BaseError) {
92
+ error = rawError;
93
+ }
94
+ // DB错误
95
+ if (rawError?.sql) {
96
+ error = new create_errors_1.CommonErrors.InternalError.DatabaseError();
97
+ }
98
+ const response = (0, exports.createSSEMessage)("error", JSON.stringify({
99
+ Response: {
100
+ Error: {
101
+ Code: error.code,
102
+ Message: error.message,
103
+ },
104
+ RequestId: requestId,
105
+ },
106
+ }));
107
+ // 完成响应
108
+ took = Date.now() - start;
109
+ logger_1.logger.log("info", `响应:${response}`);
110
+ logger_1.logger.log("info", `耗时:${took} ms - RequestId: ${requestId}`);
111
+ res.write(response);
112
+ return res.end();
113
+ }
114
+ };
115
+ };
116
+ exports.packSSE = packSSE;
117
+ const createSSEMessage = (event, message) => {
118
+ const id = (0, short_id_1.shortId)();
119
+ const idStr = `id: ${id}\n`;
120
+ const eventStr = `event: ${event}\n`;
121
+ const dataStr = `data: ${message}\n\n`;
122
+ return idStr + eventStr + dataStr;
123
+ };
124
+ exports.createSSEMessage = createSSEMessage;
@@ -0,0 +1,6 @@
1
+ export declare const logger: {
2
+ log: typeof log;
3
+ };
4
+ type LogLevel = "info" | "error" | "warning";
5
+ declare function log(level: LogLevel, message: unknown): void;
6
+ export {};
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logger = void 0;
7
+ const moment_1 = __importDefault(require("moment"));
8
+ exports.logger = { log };
9
+ function log(level, message) {
10
+ const logTime = (0, moment_1.default)().format(`YYYY-MM-DD HH:mm:ss`);
11
+ if (typeof message === "string") {
12
+ console.log(`[${level}] - [${logTime}] - ${message}`);
13
+ }
14
+ else {
15
+ console.log(`[${level}] - [${logTime}] - `, message);
16
+ }
17
+ }
@@ -0,0 +1 @@
1
+ export declare const shortId: () => string;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shortId = void 0;
4
+ const nanoid_1 = require("nanoid");
5
+ const nanoid = (0, nanoid_1.customAlphabet)("123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", 10);
6
+ const shortId = () => nanoid();
7
+ exports.shortId = shortId;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { createServer } from "./common/create-server";
2
2
  export { createErrors } from "./common/create-errors";
3
- export { implementAPI } from "./common/create-api";
3
+ export { implementAPI, ServerContext } from "./common/create-api";
4
+ export { callService } from "./common/call-service";
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.implementAPI = exports.createErrors = exports.createServer = void 0;
3
+ exports.callService = exports.implementAPI = exports.createErrors = exports.createServer = void 0;
4
4
  var create_server_1 = require("./common/create-server");
5
5
  Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return create_server_1.createServer; } });
6
6
  var create_errors_1 = require("./common/create-errors");
7
7
  Object.defineProperty(exports, "createErrors", { enumerable: true, get: function () { return create_errors_1.createErrors; } });
8
8
  var create_api_1 = require("./common/create-api");
9
9
  Object.defineProperty(exports, "implementAPI", { enumerable: true, get: function () { return create_api_1.implementAPI; } });
10
+ var call_service_1 = require("./common/call-service");
11
+ Object.defineProperty(exports, "callService", { enumerable: true, get: function () { return call_service_1.callService; } });
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "kb-server",
3
- "version": "0.0.1-beta.2",
3
+ "version": "0.0.1-beta.20",
4
4
  "description": "A fast server for Node.JS,made by express.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
- "build": "tsc",
7
+ "build": "rm -rf ./dist && tsc",
8
8
  "test": "echo \"Error: no test specified\" && exit 1"
9
9
  },
10
10
  "keywords": [
@@ -23,10 +23,14 @@
23
23
  "class-validator": "^0.14.1",
24
24
  "express": "^4.21.1",
25
25
  "is-plain-object": "^5.0.0",
26
+ "moment": "^2.30.1",
27
+ "nanoid": "^5.1.3",
28
+ "node-fetch": "^2.7.0",
26
29
  "utility-types": "^3.11.0",
27
30
  "uuid": "^11.0.3"
28
31
  },
29
32
  "devDependencies": {
30
- "@types/express": "^4.17.21"
33
+ "@types/express": "^4.17.21",
34
+ "@types/node-fetch": "^2.6.12"
31
35
  }
32
36
  }