UNPKG

erest

Version:

Easy to build api server depend on @leizm/web and express.

326 lines (325 loc) 11.4 kB
"use strict"; /** * @file API Docs * 参考 hojs * @author Yourtion Guo <yourtion@gmail.com> */ Object.defineProperty(exports, "__esModule", { value: true }); const node_assert_1 = require("node:assert"); const fs = require("node:fs"); const path = require("node:path"); const debug_1 = require("../debug"); const generate_axios_1 = require("../plugin/generate_axios"); const generate_markdown_1 = require("../plugin/generate_markdown"); const generate_postman_1 = require("../plugin/generate_postman"); const generate_swagger_1 = require("../plugin/generate_swagger"); // Generate all.json function function generateAll(data, dir, options, writer) { const filename = (0, utils_1.getPath)("all.json", options.all); // 创建一个没有循环引用的数据副本 const cleanData = { ...data, erest: undefined, // 移除循环引用 }; writer(path.resolve(dir, filename), (0, utils_1.jsonStringify)(cleanData, 2)); } const utils_1 = require("../utils"); /** 从文档获取的字段 */ const DOC_FIELD = [ "method", "path", "realPath", "examples", "middlewares", "query", "body", "params", "group", "title", "description", "response", "required", "requiredOneOf", "tested", "responseSchema", "headers", ]; /** 默认文档输出格式化 */ const docOutputFormat = (out) => out; /** 默认文档输出函数(直接写文件) */ const docWriteSync = (path, data) => fs.writeFileSync(path, data); function generateJosn(data, dir, options, writer) { const filename = (0, utils_1.getPath)("doc.json", options.json); // 创建一个没有循环引用的数据副本 const cleanData = { ...data, erest: undefined, // 移除循环引用 }; writer(path.resolve(dir, filename), (0, utils_1.jsonStringify)(cleanData, 2)); } class IAPIDoc { erest; info; groups; docsOptions; plugins = []; writer = docWriteSync; docDataCache = null; constructor(erestIns) { this.erest = erestIns; const { info, groups, docsOptions } = this.erest.privateInfo; this.info = info; this.groups = groups; this.docsOptions = docsOptions; } /** 生成类型文档 - 支持新的 Zod 实现 */ generateTypeDocumentation(data) { // 从类型注册表中获取所有注册的类型 const typeRegistry = this.erest.typeRegistry; if (typeRegistry && typeRegistry.size > 0) { for (const [typeName, zodSchema] of typeRegistry.entries()) { const typeDoc = { name: typeName, description: this.extractZodSchemaDescription(zodSchema), isBuiltin: false, tsType: this.extractTypeScriptType(zodSchema), isDefaultFormat: true, isParamsRequired: false, }; data.types[typeName] = typeDoc; } } // 从schema注册表中获取所有注册的schema const schemaRegistry = this.erest.schemaRegistry; if (schemaRegistry && schemaRegistry.size > 0) { for (const [schemaName, zodSchema] of schemaRegistry.entries()) { const schemaDoc = { name: schemaName, description: this.extractZodSchemaDescription(zodSchema), isBuiltin: false, tsType: this.extractTypeScriptType(zodSchema), isDefaultFormat: true, isParamsRequired: false, }; data.types[schemaName] = schemaDoc; } } } /** 从Zod Schema中提取描述信息 */ extractZodSchemaDescription(zodSchema) { if (!zodSchema || !zodSchema._def) { return "未知类型"; } const typeName = zodSchema._def.typeName || zodSchema._def.type; switch (typeName) { case "ZodString": case "string": return "字符串类型"; case "ZodNumber": case "number": return "数字类型"; case "ZodBoolean": case "boolean": return "布尔类型"; case "ZodDate": case "date": return "日期类型"; case "ZodArray": case "array": return "数组类型"; case "ZodObject": case "object": return "对象类型"; case "ZodEnum": case "enum": return "枚举类型"; case "ZodUnion": case "union": return "联合类型"; case "ZodOptional": case "optional": return "可选类型"; case "ZodNullable": case "nullable": return "可空类型"; default: return `Zod ${typeName} 类型`; } } /** 从Zod Schema中提取TypeScript类型 */ extractTypeScriptType(zodSchema) { if (!zodSchema || !zodSchema._def) { return "unknown"; } const typeName = zodSchema._def.typeName || zodSchema._def.type; switch (typeName) { case "ZodString": case "string": return "string"; case "ZodNumber": case "number": return "number"; case "ZodBoolean": case "boolean": return "boolean"; case "ZodDate": case "date": return "Date"; case "ZodArray": case "array": { const defObj = zodSchema._def; const elementType = defObj.element || defObj.type || defObj.innerType; const innerType = elementType ? this.extractTypeScriptType(elementType) : "unknown"; return `${innerType}[]`; } case "ZodObject": case "object": return "object"; case "ZodEnum": case "enum": { const enumValues = zodSchema._def.values || zodSchema._def.entries; if (Array.isArray(enumValues)) { return enumValues.map((v) => (typeof v === "string" ? `"${v}"` : String(v))).join(" | "); } else if (enumValues && typeof enumValues === "object") { // 处理 { red: 'red', green: 'green', blue: 'blue' } 格式 const values = Object.values(enumValues); return values.map((v) => (typeof v === "string" ? `"${v}"` : String(v))).join(" | "); } return "string"; } case "ZodUnion": case "union": { const unionTypes = zodSchema._def.options; if (Array.isArray(unionTypes)) { return unionTypes.map((t) => this.extractTypeScriptType(t)).join(" | "); } return "unknown"; } case "ZodOptional": case "optional": return `${this.extractTypeScriptType(zodSchema._def.innerType)} | undefined`; case "ZodNullable": case "nullable": return `${this.extractTypeScriptType(zodSchema._def.innerType)} | null`; default: return "unknown"; } } /** 获取文档数据 */ buildDocData() { if (this.docDataCache) return this.docDataCache; (0, debug_1.docs)("data"); const now = new Date(); const data = { info: this.info, // FIXME: 对日期格式需要优化 genTime: `${now.toLocaleDateString()} ${now.toLocaleTimeString()}`, errorManager: this.erest.errors, schema: this.erest.schema, typeManager: this.erest.type, group: this.groups, types: {}, apis: {}, apiInfo: { count: 0, tested: 0, untest: [], }, erest: this.erest, // 添加erest实例引用 }; const formatOutput = this.erest.api.docOutputForamt || docOutputFormat; // 生成类型文档 - 支持新的 Zod 实现 this.generateTypeDocumentation(data); for (const [k, schema] of this.erest.api.$apis.entries()) { const o = schema.options; data.apis[k] = {}; for (const key of DOC_FIELD) { data.apis[k][key] = o[key]; } const examples = data.apis[k].examples; if (examples) { examples.forEach((item) => { if (item && typeof item === "object") { item.output = formatOutput(item.output); } }); } } this.docDataCache = data; return data; } /** 设置文档输出函数 */ setWritter(writer) { this.writer = writer; } /** 生成文档 */ genDocs() { (0, debug_1.docs)("genDocs"); if (this.docsOptions.markdown) { this.registerPlugin("markdown", generate_markdown_1.default); } if (this.docsOptions.swagger) { this.registerPlugin("swagger", generate_swagger_1.default); } if (this.docsOptions.postman) { this.registerPlugin("postman", generate_postman_1.default); } if (this.docsOptions.json) { this.registerPlugin("json", generateJosn); } if (this.docsOptions.axios) { this.registerPlugin("axios", generate_axios_1.default); } if (this.docsOptions.all) { this.registerPlugin("all", generateAll); } return this; } getSwaggerInfo() { return (0, generate_swagger_1.buildSwagger)(this.buildDocData()); } registerPlugin(name, plugin) { (0, debug_1.docs)(name); this.plugins.push(plugin); } /** 保存文档 */ save(dir) { (0, node_assert_1.strict)(typeof dir === "string" && dir.length > 0, `文档存储目录"${dir}"格式不正确:必须是字符串类型`); // 保存 all.json const data = this.buildDocData(); for (const [key, api] of Object.entries(data.apis)) { data.apiInfo.count += 1; if (api.examples && api.examples.length > 0) { data.apiInfo.tested += 1; } else { data.apiInfo.untest.push(key); } } (0, debug_1.docs)("save: %s", dir); // 根据插件生成文档 for (const fn of this.plugins) { (0, debug_1.docs)("build doc: %s", fn); // 防止文档生成插件报错 try { fn(data, dir, this.docsOptions, this.writer); } catch (error) { console.error(error); } } return this; } /** 当进程退出时存储文档 */ saveOnExit(dir) { (0, debug_1.docs)("saveOnExit: %s", dir); process.on("exit", () => this.save(dir)); return this; } } exports.default = IAPIDoc;