@jayfong/x-server 2.32.0 → 2.35.0

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.
@@ -34,58 +34,74 @@ export function defineSliceTask(options) {
34
34
  }
35
35
  }
36
36
  });
37
- const redisKeyPrefix = `${x.appId}_batch_task_${options.name}`;
38
- const res = {
39
- add: async (data, addOptions) => {
40
- const key = (addOptions == null ? void 0 : addOptions.key) || '';
41
- const duration = (addOptions == null ? void 0 : addOptions.duration) != null ? ms(addOptions.duration) : typeof options.duration === 'function' ? ms(options.duration(key)) : options.duration && ms(options.duration);
42
- const threshold = (addOptions == null ? void 0 : addOptions.threshold) || typeof options.threshold && (typeof options.threshold === 'function' ? options.threshold(key) : options.threshold);
43
- const thresholdTimeout = (addOptions == null ? void 0 : addOptions.thresholdTimeout) != null ? ms(addOptions.thresholdTimeout) : typeof options.thresholdTimeout === 'function' ? ms(options.thresholdTimeout(key)) : options.thresholdTimeout && ms(options.thresholdTimeout);
44
- assert(duration != null || threshold != null, '参数 threshold 和 duration 必须至少设置 1 个');
45
- const redisKey = !key ? redisKeyPrefix : `${redisKeyPrefix}_${key}`;
46
- const res = await x.redis.multi([['llen', redisKey], ['lpush', redisKey, JSON.stringify(data)]]).exec();
47
- const count = parseInt(res[0][1], 10) + 1;
37
+ const addAction = async (data, addOptions) => {
38
+ const redisKeyPrefix = `${x.appId}_batch_task_${options.name}`;
39
+ const key = (addOptions == null ? void 0 : addOptions.key) || '';
40
+ const duration = (addOptions == null ? void 0 : addOptions.duration) != null ? ms(addOptions.duration) : typeof options.duration === 'function' ? ms(options.duration(key)) : options.duration && ms(options.duration);
41
+ const threshold = (addOptions == null ? void 0 : addOptions.threshold) || typeof options.threshold && (typeof options.threshold === 'function' ? options.threshold(key) : options.threshold);
42
+ const thresholdTimeout = (addOptions == null ? void 0 : addOptions.thresholdTimeout) != null ? ms(addOptions.thresholdTimeout) : typeof options.thresholdTimeout === 'function' ? ms(options.thresholdTimeout(key)) : options.thresholdTimeout && ms(options.thresholdTimeout);
43
+ assert(duration != null || threshold != null, '参数 threshold duration 必须至少设置 1 ');
44
+ const redisKey = !key ? redisKeyPrefix : `${redisKeyPrefix}_${key}`;
45
+ const res = await x.redis.multi([['llen', redisKey], ['lpush', redisKey, JSON.stringify(data)]]).exec();
46
+ const count = parseInt(res[0][1], 10) + 1;
48
47
 
49
- // 仅时段
50
- // 1分钟内的合并推送
51
- if (duration != null) {
52
- if (count === 1) {
53
- await task.add({
54
- key: key,
55
- redisKey: redisKey
56
- }, {
57
- delay: duration
58
- });
59
- }
48
+ // 仅时段
49
+ // 1分钟内的合并推送
50
+ if (duration != null) {
51
+ if (count === 1) {
52
+ await task.add({
53
+ key: key,
54
+ redisKey: redisKey
55
+ }, {
56
+ delay: duration
57
+ });
60
58
  }
61
- // 仅阈值
62
- // 满10条推送
63
- else if (threshold != null) {
64
- const delayTaskId = redisKey;
65
- const prevJob = await task.getJob(delayTaskId);
59
+ }
60
+ // 仅阈值
61
+ // 满10条推送
62
+ else if (threshold != null) {
63
+ const delayTaskId = redisKey;
64
+ const prevJob = await task.getJob(delayTaskId);
66
65
 
67
- // 到达阈值,立即发送
68
- if (count === threshold) {
69
- await (prevJob == null ? void 0 : prevJob.remove());
66
+ // 到达阈值,立即发送
67
+ if (count === threshold) {
68
+ await (prevJob == null ? void 0 : prevJob.remove());
69
+ await task.add({
70
+ key: key,
71
+ redisKey: redisKey
72
+ });
73
+ }
74
+ // 存在超时设置
75
+ else if (thresholdTimeout) {
76
+ if (!prevJob) {
70
77
  await task.add({
71
78
  key: key,
72
79
  redisKey: redisKey
80
+ }, {
81
+ jobId: delayTaskId,
82
+ delay: thresholdTimeout
73
83
  });
74
84
  }
75
- // 存在超时设置
76
- else if (thresholdTimeout) {
77
- if (!prevJob) {
78
- await task.add({
79
- key: key,
80
- redisKey: redisKey
81
- }, {
82
- jobId: delayTaskId,
83
- delay: thresholdTimeout
84
- });
85
- }
86
- }
87
85
  }
88
86
  }
89
87
  };
88
+ const addTask = defineTask({
89
+ name: `${options.name}_add`,
90
+ concurrency: 1,
91
+ handle: async payload => {
92
+ await addAction(payload.data, payload.options);
93
+ }
94
+ });
95
+ const res = {
96
+ add: async (data, addOptions) => {
97
+ const delay = addOptions.delay && ms(addOptions.delay) || 0;
98
+ await addTask.add({
99
+ data: data,
100
+ options: addOptions
101
+ }, {
102
+ delay: delay
103
+ });
104
+ }
105
+ };
90
106
  return res;
91
107
  }
@@ -1,6 +1,6 @@
1
1
  import * as vae from 'vtils/vae';
2
2
  import type { XHandler } from './types';
3
- export type { HandlerMethodMap, HandlerPath, HandlerPayloadMap, HandlerResultMap } from '.x/routes';
3
+ export type { HandlerMethodMap, HandlerPath, HandlerPayloadMap, HandlerResultMap, } from '.x/routes';
4
4
  export declare class Handler<TReqData extends any = void, TResData extends any = void, TReqMethod extends XHandler.Method = XHandler.Method> {
5
5
  readonly options: XHandler.Options<TReqData, TResData, TReqMethod>;
6
6
  private requestDataSchema;
@@ -1,12 +1,10 @@
1
1
  import LZString from 'lz-string';
2
- import { DataPacker } from 'vtils';
2
+ import { DataPacker, base64UrlEncode, rot13 } from 'vtils';
3
3
  import * as vae from 'vtils/vae';
4
4
  import { getZhCN, yup } from 'vtils/validator';
5
5
  import { HttpError } from "../core/http_error";
6
6
  import { DisposeService } from "../services/dispose";
7
-
8
- // @ts-ignore
9
- // prettier-ignore
7
+ import { Server } from "./server";
10
8
  yup.setLocale(getZhCN({
11
9
  getLabel: params => params.label || params.path
12
10
  }));
@@ -74,9 +72,18 @@ export class Handler {
74
72
  '_#': LZString.compress(JSON.stringify(res))
75
73
  };
76
74
  }
75
+
76
+ // 加密返回数据
77
+ if (this.options.responseDataEncrypt && Server.options.responseEncryptAlgorithm) {
78
+ if (Server.options.responseEncryptAlgorithm === 'simple') {
79
+ res = {
80
+ _$: rot13(base64UrlEncode(`${Date.now()}${JSON.stringify(res)}`))
81
+ };
82
+ }
83
+ }
77
84
  return res == null ? {} : res;
78
85
  };
79
- this.handleWs = async (data, ctx) => {
86
+ this.handleWs = async (_data, ctx) => {
80
87
  const dispose = new DisposeService();
81
88
  ctx.ws.socket.on('message', async payload => {
82
89
  try {
@@ -1,8 +1,8 @@
1
1
  import Fastify from 'fastify';
2
2
  import { base64UrlDecode, castArray, keyBy, noop, rot13 } from 'vtils';
3
- import { HandlerMethodToHttpMethod } from "./http_method";
4
- import { HttpError } from "./http_error";
5
3
  import { x } from "../x";
4
+ import { HttpError } from "./http_error";
5
+ import { HandlerMethodToHttpMethod } from "./http_method";
6
6
  export class Server {
7
7
  constructor(options) {
8
8
  this.options = options;
@@ -91,7 +91,6 @@ export class Server {
91
91
  if (handlerMethod === 'FILE') {
92
92
  const part = await req.file();
93
93
  files = Object.keys(part.fields).reduce((res, name) => {
94
- ;
95
94
  res[name] = castArray(part.fields[name]).map(item => item.file ? item : item.value)[0];
96
95
  return res;
97
96
  }, {});
@@ -1,12 +1,12 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node/http" />
3
3
  /// <reference types="got/dist/source/core/utils/timed-out" />
4
+ import type { IncomingHttpHeaders } from 'http';
4
5
  import type { MultipartFile } from '@fastify/multipart';
5
6
  import type { SocketStream } from '@fastify/websocket';
6
7
  import type { Queue } from 'bull';
7
8
  import { CronJob } from 'cron';
8
9
  import { FastifyReply, FastifyRequest, FastifyServerOptions } from 'fastify';
9
- import type { IncomingHttpHeaders } from 'http';
10
10
  import type { MsValue } from 'vtils';
11
11
  import type { AsyncOrSync, LiteralUnion, OneOrMore, RequiredDeep } from 'vtils/types';
12
12
  import * as vae from 'vtils/vae';
@@ -36,6 +36,14 @@ export declare namespace XServer {
36
36
  * 监听端口
37
37
  */
38
38
  port: number;
39
+ /**
40
+ * 响应加密算法
41
+ */
42
+ responseEncryptAlgorithm?: 'simple';
43
+ /**
44
+ * 响应加密密钥
45
+ */
46
+ responseEncryptKey?: string;
39
47
  /**
40
48
  * 透传给 Fastify 的选项
41
49
  */
@@ -126,6 +134,7 @@ export declare namespace XHandler {
126
134
  * @default false
127
135
  */
128
136
  responseDataObfuscate?: boolean;
137
+ responseDataEncrypt?: boolean;
129
138
  /**
130
139
  * 响应的 Content-Type 头
131
140
  */
@@ -199,6 +208,7 @@ export declare namespace XTask {
199
208
  duration?: MsValue;
200
209
  threshold?: number;
201
210
  thresholdTimeout?: MsValue;
211
+ delay?: MsValue;
202
212
  }) => Promise<void>;
203
213
  }
204
214
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayfong/x-server",
3
- "version": "2.32.0",
3
+ "version": "2.35.0",
4
4
  "license": "ISC",
5
5
  "sideEffects": false,
6
6
  "main": "lib/_cjs/index.js",
@@ -9,9 +9,7 @@
9
9
  "bin": {
10
10
  "xs": "lib/_cjs/cli/cli.js"
11
11
  },
12
- "files": [
13
- "lib"
14
- ],
12
+ "files": ["lib"],
15
13
  "scripts": {
16
14
  "build": "haoma compile",
17
15
  "build_test_pkg": "tyn build && rm -rf ./lib_test && mkdir -p ./lib_test && cp -r ./lib ./lib_test/lib && cp ./package.json ./lib_test/package.json && cd ./tests/app && tyn add file:../../lib_test",
@@ -19,16 +17,14 @@
19
17
  "test": "tsc --noEmit -p ./tsconfig.build.json && jest \"$(pwd)/src/\"",
20
18
  "test_all": "tsc --noEmit -p ./tsconfig.build.json && jest",
21
19
  "test_api_bot": "cd /Users/admin/Documents/jfWorks/qiqi-bot && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api",
22
- "test_api_qiqi": "cd /Users/admin/Documents/jfWorks/qiqi-server && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api",
23
- "test_api1_bot": "cd /Users/admin/Documents/jfWorks/qiqi-bot && DEBUG=api1 tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api1",
24
- "test_api1_qiqi": "cd /Users/admin/Documents/jfWorks/qiqi-server && DEBUG=api1 tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api1"
20
+ "test_api_qiqi": "cd /Users/admin/Documents/jfWorks/qiqi-server && DEBUG=api tsx /Users/admin/Documents/jfWorks/x/packages/x-server/src/cli/cli.ts api"
25
21
  },
26
22
  "dependencies": {
27
23
  "@fastify/cors": "^8.3.0",
28
24
  "@fastify/formbody": "^7.4.0",
29
25
  "@fastify/multipart": "^7.7.0",
30
26
  "@fastify/websocket": "^8.1.0",
31
- "@prisma/client": "^4.8.0",
27
+ "@prisma/client": "^5.8.0",
32
28
  "@types/busboy": "^0.3.2",
33
29
  "@types/http-errors": "^1.8.2",
34
30
  "@types/jsonwebtoken": "^8.5.8",
@@ -62,7 +58,7 @@
62
58
  "nodemailer": "^6.7.3",
63
59
  "pino-pretty": "^10.0.1",
64
60
  "pirates": "^4.0.6",
65
- "prisma": "^4.8.0",
61
+ "prisma": "^5.8.0",
66
62
  "select-run": "^1.1.2",
67
63
  "supports-color": "^8",
68
64
  "svg-captcha": "^1.4.0",
@@ -85,7 +81,8 @@
85
81
  "ioredis-mock": "^8.7.0",
86
82
  "json-xml-parse": "^1.2.4",
87
83
  "node-fetch": "^3.3.1",
88
- "npm-check-updates": "^12.5.9"
84
+ "npm-check-updates": "^12.5.9",
85
+ "openapi-types": "^12.1.3"
89
86
  },
90
87
  "publishConfig": {
91
88
  "access": "public"
@@ -1,286 +0,0 @@
1
- "use strict";
2
-
3
- var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
4
- var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
5
- exports.__esModule = true;
6
- exports.ApiGenerator1 = void 0;
7
- var _nodePath = _interopRequireDefault(require("node:path"));
8
- var parseComment = _interopRequireWildcard(require("comment-parser"));
9
- var _debug = _interopRequireDefault(require("debug"));
10
- var _fsExtra = _interopRequireDefault(require("fs-extra"));
11
- var ts = _interopRequireWildcard(require("ts-morph"));
12
- var _vtils = require("vtils");
13
- var _http_method = require("../core/http_method");
14
- class ApiGenerator1 {
15
- constructor() {
16
- this.debug = (0, _debug.default)('api1');
17
- this.cwd = process.cwd();
18
- // cwd = '/Users/admin/Documents/jfWorks/x-server-test'
19
- this.project = void 0;
20
- }
21
- getTypeBySymbol(symbol) {
22
- return symbol.getTypeAtLocation(symbol.getDeclarations()[0] || symbol.getValueDeclarationOrThrow());
23
- }
24
- getComment(declaration) {
25
- var _declaration$getLeadi;
26
- const text = ((declaration == null || (_declaration$getLeadi = declaration.getLeadingCommentRanges()[0]) == null ? void 0 : _declaration$getLeadi.getText()) || '').trim();
27
- const comment = parseComment.parse(text)[0];
28
- const description = (comment == null ? void 0 : comment.description) || '';
29
- const tags = new Map();
30
- comment == null ? void 0 : comment.tags.forEach(tag => {
31
- tags.set(tag.tag, tag.description);
32
- });
33
- return {
34
- existing: !!comment,
35
- description: description,
36
- tags: tags
37
- };
38
- }
39
- getCommentBySymbol(symbol) {
40
- var _this$getComment;
41
- return ((_this$getComment = this.getComment(symbol.getDeclarations()[0])) == null ? void 0 : _this$getComment.description) || '';
42
- }
43
- typeToApiData(type, _symbol) {
44
- var _type$getSymbol, _type$getSymbol2, _type$getSymbol3;
45
- // ws
46
- if (((_type$getSymbol = type.getSymbol()) == null ? void 0 : _type$getSymbol.getName()) === 'SocketStream') {
47
- return {
48
- name: 'ws',
49
- desc: 'ws',
50
- required: false,
51
- type: 'object',
52
- children: [],
53
- enum: []
54
- };
55
- }
56
-
57
- // XFile
58
- if (((_type$getSymbol2 = type.getSymbol()) == null ? void 0 : _type$getSymbol2.getName()) === 'MultipartFile') {
59
- const symbol = _symbol || type.getSymbol();
60
- return {
61
- name: 'file',
62
- desc: symbol && this.getCommentBySymbol(symbol) || '',
63
- required: !!symbol && !(symbol.getFlags() & ts.SymbolFlags.Optional),
64
- type: 'file',
65
- children: [],
66
- enum: []
67
- };
68
- }
69
- let isRequired = true;
70
- let isUnion = type.isUnion();
71
- const unionTypes = isUnion ? type.getUnionTypes().filter(item => !item.isBooleanLiteral() && !item.isNull() && !item.isUndefined()) : [];
72
- isUnion = !!unionTypes.length;
73
- if (isUnion) {
74
- if (unionTypes.length === 1 && !unionTypes[0].isLiteral()) {
75
- isUnion = false;
76
- }
77
- // 兼容 prisma 生成的类型用 null 表示可选
78
- isRequired = unionTypes.length === type.getUnionTypes().length;
79
- // 必须用 getBaseTypeOfLiteralType 获取枚举字面量的原始类型
80
- type = unionTypes[0].getBaseTypeOfLiteralType();
81
- }
82
- const isEnum = type.isEnum();
83
- const enumData = isEnum ?
84
- // @ts-ignore
85
- type.compilerType.types.reduce((res, item) => {
86
- res[item.getSymbol().getName()] = item.value;
87
- return res;
88
- }, {}) : {};
89
- const enumKeys = Object.keys(enumData);
90
- const enumValues = Object.values(enumData);
91
- const isIntersection = type.isIntersection();
92
- const intersectionTypes = isIntersection && type.getIntersectionTypes();
93
- const isArray = type.isArray();
94
- const isString = isEnum ? typeof enumValues[0] === 'string' : type.isString() || ['Date'].includes(((_type$getSymbol3 = type.getSymbol()) == null ? void 0 : _type$getSymbol3.getName()) || '');
95
- const isNumber = isEnum ? typeof enumValues[0] === 'number' : type.isNumber();
96
- const isBoolean = type.isBoolean() || type.isUnion() && type.getUnionTypes().some(item => item.isBooleanLiteral()) && type.getUnionTypes().every(item => item.isBooleanLiteral() || item.isNull() || item.isUndefined());
97
- const isObject = !isArray && !isString && !isNumber && !isBoolean && type.isObject() ||
98
- // 将交集类型视为对象
99
- isIntersection;
100
- const symbol = _symbol || type.getSymbol();
101
- const parentSymbol = symbol;
102
- const apiName = (symbol == null ? void 0 : symbol.getName()) || '__type';
103
- const apiDesc = [symbol && this.getCommentBySymbol(symbol), isEnum && `枚举:${enumKeys.map((key, index) => `${key}->${enumValues[index]}`).join('; ')}`].filter(Boolean).join('\n');
104
- const apiEnum = isUnion ? unionTypes.map(t => t.getLiteralValue()) : isEnum ? enumValues : [];
105
- const apiType = isArray ? 'array' : isString ? 'string' : isNumber ? 'number' : isBoolean ? 'boolean' : 'object';
106
- const apiRequired = isRequired === false ? false : !!symbol && !(symbol.getFlags() & ts.SymbolFlags.Optional);
107
- const apiChildren = isArray ? [this.typeToApiData(type.getArrayElementTypeOrThrow())] : isObject ? (0, _vtils.ii)(() => {
108
- const context = type._context;
109
- const compilerFactory = context.compilerFactory;
110
- const rawChecker = type.compilerType.checker;
111
- let symbols = [];
112
- if (intersectionTypes) {
113
- // https://github.com/microsoft/TypeScript/issues/38184
114
- symbols = rawChecker.getAllPossiblePropertiesOfTypes(intersectionTypes.map(item => item.compilerType))
115
- // https://github.com/dsherret/ts-morph/blob/a7072fcf6f9babb784b40f0326c80dea4563a4aa/packages/ts-morph/src/compiler/types/Type.ts#L296
116
- .map(symbol => compilerFactory.getSymbol(symbol));
117
- } else {
118
- // symbols = type.getApparentProperties()
119
- // https://github.com/microsoft/TypeScript/issues/38184
120
- symbols = rawChecker.getAllPossiblePropertiesOfTypes([type.compilerType])
121
- // https://github.com/dsherret/ts-morph/blob/a7072fcf6f9babb784b40f0326c80dea4563a4aa/packages/ts-morph/src/compiler/types/Type.ts#L296
122
- .map(symbol => compilerFactory.getSymbol(symbol));
123
- }
124
- return symbols.map(symbol => {
125
- return this.typeToApiData(!symbol.compilerSymbol.declarations ?
126
- // 对于复杂对象,没有定义的,通过 type 直接获取(在前面通过 getText 预处理得到)
127
- symbol.compilerSymbol.type ? compilerFactory.getType(symbol.compilerSymbol.type) :
128
- // fix: symbol.compilerSymbol.type 为 undefined
129
- // https://github.com/styleguidist/react-docgen-typescript/blob/master/src/parser.ts#L696
130
- compilerFactory.getType(rawChecker.getTypeOfSymbolAtLocation(
131
- // @ts-ignore
132
- symbol.compilerSymbol,
133
- // @ts-ignore
134
- parentSymbol.compilerSymbol.declarations[0])) : this.getTypeBySymbol(symbol), symbol);
135
- });
136
- }) : [];
137
- return {
138
- name: apiName,
139
- desc: apiDesc,
140
- enum: apiEnum,
141
- type: apiType,
142
- required: apiRequired,
143
- children: apiChildren
144
- };
145
- }
146
- apiDataToJsonSchema(apiData, jsonSchema = {}) {
147
- jsonSchema.description = apiData.desc;
148
- if (apiData.type === 'object') {
149
- jsonSchema.type = 'object';
150
- jsonSchema.properties = apiData.children.reduce((res, item) => {
151
- res[item.name] = this.apiDataToJsonSchema(item);
152
- return res;
153
- }, {});
154
- jsonSchema.required = apiData.children.filter(item => item.required).map(item => item.name);
155
- } else if (apiData.type === 'array') {
156
- jsonSchema.type = 'array';
157
- jsonSchema.items = apiData.children.map(item => this.apiDataToJsonSchema(item))[0];
158
- } else {
159
- jsonSchema.type = apiData.type;
160
- if (apiData.enum.length) {
161
- jsonSchema.enum = apiData.enum;
162
- }
163
- }
164
- return jsonSchema;
165
- }
166
- genYApiData(handles) {
167
- const data = {};
168
- for (const handle of handles) {
169
- data[handle.category] = data[handle.category] || [];
170
- data[handle.category].push(handle.handlerMethod === 'GET' ? {
171
- method: handle.method.toUpperCase(),
172
- title: handle.name,
173
- path: handle.path,
174
- res_body_type: 'json',
175
- req_body_is_json_schema: false,
176
- res_body_is_json_schema: true,
177
- req_params: [],
178
- req_query: handle.requestData.children.map(item => ({
179
- required: item.required ? 1 : 0,
180
- name: item.name,
181
- desc: item.desc,
182
- type: 'string'
183
- })),
184
- req_headers: [],
185
- req_body_form: [],
186
- res_body: JSON.stringify(handle.responseDataJsonSchema)
187
- } : handle.handlerMethod === 'FILE' ? {
188
- method: handle.method.toUpperCase(),
189
- title: handle.name,
190
- path: handle.path,
191
- req_body_type: 'form',
192
- res_body_type: 'json',
193
- req_body_is_json_schema: false,
194
- res_body_is_json_schema: true,
195
- req_params: [],
196
- req_query: [],
197
- req_headers: [],
198
- req_body_form: handle.requestData.children.map(item => ({
199
- required: item.required ? 1 : 0,
200
- name: item.name,
201
- desc: item.desc,
202
- type: item.type === 'file' ? 'file' : 'text'
203
- })),
204
- res_body: JSON.stringify(handle.responseDataJsonSchema)
205
- } : {
206
- method: handle.method.toUpperCase(),
207
- title: handle.name,
208
- path: handle.path,
209
- req_body_type: 'json',
210
- res_body_type: 'json',
211
- req_body_is_json_schema: true,
212
- res_body_is_json_schema: true,
213
- req_params: [],
214
- req_query: [],
215
- req_headers: [],
216
- req_body_form: [],
217
- req_body_other: JSON.stringify(handle.requestDataJsonSchema),
218
- res_body: JSON.stringify(handle.responseDataJsonSchema)
219
- });
220
- }
221
- return Object.keys(data).map(cat => ({
222
- name: cat,
223
- desc: cat,
224
- list: data[cat]
225
- }));
226
- }
227
- async generate() {
228
- this.debug('启动项目...');
229
- this.project = new ts.Project({
230
- tsConfigFilePath: _nodePath.default.join(this.cwd, 'tsconfig.json')
231
- });
232
- this.debug('加载文件...');
233
- const sourceFile = this.project.createSourceFile(_nodePath.default.join(this.cwd, 'src/generated/handlers.ts'), await _fsExtra.default.readFile(_nodePath.default.join(this.cwd, 'node_modules/.x/handlers.ts'), 'utf-8'));
234
- this.debug('导出处理器...');
235
- const handlerGroup = sourceFile.getExportSymbols();
236
- const handles = [];
237
- this.debug('生成API文档...');
238
- for (const handlerList of handlerGroup) {
239
- const handlerListSourceFile = this.getTypeBySymbol(handlerList).getProperties()[0].getDeclarations()[0].getSourceFile();
240
- const basePath = `/${handlerListSourceFile.getFilePath().replace('.ts', '').split('/src/handlers/')[1].replace(/(^|\/)index$/, '/').split('/').map(v => (0, _vtils.snakeCase)(v)).join('/')}/`.replace(/\/{2,}/g, '/');
241
- for (const handler of handlerListSourceFile.getVariableStatements().filter(item => item.isExported())) {
242
- // 重要:这一步必须,先调一遍 getText 获取看到的对象,后续对于复杂定义才不会报错
243
- handler.getDeclarations().forEach(exp => {
244
- exp.getType().getText();
245
- });
246
- const handlerExp = handler.getDeclarations()[0];
247
- const handlerPath = `${basePath}${handlerExp.getName()}`;
248
- this.debug('生成接口: %s ...', handlerPath);
249
- const [requestType, responseType, methodType] = handlerExp.getType().getTypeArguments();
250
- const handlerComment = this.getComment(handlerExp.getParent().getParent());
251
- if (handlerComment.tags.has('private')) {
252
- this.debug('跳过生成接口: %s', `${handlerPath}`);
253
- continue;
254
- }
255
- const handlerCategory = (0, _vtils.pascalCase)(basePath) || 'Index';
256
- const handlerName = handlerComment.description || handlerPath;
257
- const handlerMethod = methodType.getLiteralValueOrThrow();
258
- const serverMethod = _http_method.HandlerMethodToHttpMethod[handlerMethod];
259
- const requestData = this.typeToApiData(requestType);
260
- const responseData = this.typeToApiData(responseType);
261
- const requestDataJsonSchema = this.apiDataToJsonSchema(requestData);
262
- const responseDataJsonSchema = this.apiDataToJsonSchema(responseData);
263
- handles.push({
264
- category: handlerCategory,
265
- name: handlerName,
266
- path: handlerPath,
267
- handlerMethod: handlerMethod,
268
- method: serverMethod,
269
- requestData: requestData,
270
- responseData: responseData,
271
- requestDataJsonSchema: requestDataJsonSchema,
272
- responseDataJsonSchema: responseDataJsonSchema
273
- });
274
- }
275
- }
276
- this.debug('写入文件...');
277
- await Promise.all([_fsExtra.default.outputJSON(_nodePath.default.join(this.cwd, 'temp/api1.json'), handles, {
278
- spaces: 2
279
- }), _fsExtra.default.outputJSON(_nodePath.default.join(this.cwd, 'temp/yapi1.json'), this.genYApiData(handles), {
280
- spaces: 2
281
- })]);
282
- }
283
- }
284
-
285
- // new ApiGenerator().generate()
286
- exports.ApiGenerator1 = ApiGenerator1;
@@ -1,44 +0,0 @@
1
- import createDebug from 'debug';
2
- import type { JSONSchema4 } from 'json-schema';
3
- import * as ts from 'ts-morph';
4
- import type { XHandler } from '../core/types';
5
- interface ApiData {
6
- name: string;
7
- desc: string;
8
- type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'file';
9
- enum: any[];
10
- required: boolean;
11
- children: ApiData[];
12
- }
13
- type ApiHandles = Array<{
14
- category: string;
15
- name: string;
16
- path: string;
17
- handlerMethod: XHandler.Method;
18
- method: string;
19
- requestData: ApiData;
20
- responseData: ApiData;
21
- requestDataJsonSchema: JSONSchema4;
22
- responseDataJsonSchema: JSONSchema4;
23
- }>;
24
- export declare class ApiGenerator1 {
25
- debug: createDebug.Debugger;
26
- cwd: string;
27
- project: ts.Project;
28
- getTypeBySymbol(symbol: ts.Symbol): ts.Type;
29
- getComment(declaration?: ts.Node): {
30
- existing: boolean;
31
- description: string;
32
- tags: Map<string, string>;
33
- };
34
- getCommentBySymbol(symbol: ts.Symbol): string;
35
- typeToApiData(type: ts.Type, _symbol?: ts.Symbol): ApiData;
36
- apiDataToJsonSchema(apiData: ApiData, jsonSchema?: JSONSchema4): JSONSchema4;
37
- genYApiData(handles: ApiHandles): {
38
- name: string;
39
- desc: string;
40
- list: any;
41
- }[];
42
- generate(): Promise<void>;
43
- }
44
- export {};