@alova/wormhole
Version:
More modern openAPI generating solution for alova.js
294 lines (293 loc) • 11.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.zConfig = exports.zGeneratorConfig = exports._zGeneratorConfig = exports.zApiPlugin = exports.zFetchOptions = exports.zHandleApi = exports.zApiDescriptor = exports.zPlatformType = exports.zTemplateType = exports.zConfigType = void 0;
const node_path_1 = __importDefault(require("node:path"));
const v3_1 = require("zod/v3"); // v4版本不稳定,暂时使用v3
const loader_1 = require("../../core/loader");
/**
* Find the corresponding input attribute value
*/
exports.zConfigType = v3_1.z.enum(['auto', 'ts', 'typescript', 'module', 'commonjs']);
/**
* template type
*/
exports.zTemplateType = v3_1.z.enum(['typescript', 'module', 'commonjs']);
/**
* platform type
*/
exports.zPlatformType = v3_1.z.enum(['swagger', 'knife4j', 'yapi']);
exports.zApiDescriptor = v3_1.z.any();
exports.zHandleApi = v3_1.z
.function()
.args(exports.zApiDescriptor)
.returns(v3_1.z.union([exports.zApiDescriptor, v3_1.z.undefined(), v3_1.z.null(), v3_1.z.void()]));
exports.zFetchOptions = v3_1.z.record(v3_1.z.string(), v3_1.z.any());
// Helper function for MaybePromise return types
function zMaybePromise(schema) {
return v3_1.z.union([schema, v3_1.z.promise(schema)]);
}
// Common return type for plugin hooks
function zPluginReturn(schema) {
return zMaybePromise(v3_1.z.union([schema, v3_1.z.undefined(), v3_1.z.null(), v3_1.z.void()]));
}
// 定义 OpenAPIDocument 类型(简化版本,因为完整的 OpenAPI 规范非常复杂)
const zOpenAPIDocument = v3_1.z.any();
exports.zApiPlugin = v3_1.z.object({
name: v3_1.z.string().optional(),
config: v3_1.z.lazy(() => v3_1.z.function()
.args(exports._zGeneratorConfig)
.returns(zPluginReturn(exports._zGeneratorConfig))
.optional()),
beforeOpenapiParse: v3_1.z.lazy(() => v3_1.z.function()
.args(exports._zGeneratorConfig)
.returns(v3_1.z.void())
.optional()),
afterOpenapiParse: v3_1.z.function()
.args(zOpenAPIDocument)
.returns(zPluginReturn(zOpenAPIDocument))
.optional(),
beforeCodeGenerate: v3_1.z.function()
.args(v3_1.z.any(), v3_1.z.string())
.returns(zPluginReturn(v3_1.z.string()))
.optional(),
afterCodeGenerate: v3_1.z.function()
.args(v3_1.z.instanceof(Error).optional())
.returns(v3_1.z.void())
.optional(),
});
exports._zGeneratorConfig = v3_1.z.object({
/**
* Openapi file path, it supports json and yaml file, and network url
* @requires true
*
* @example
* input: 'http://localhost:3000/openapi.json'
* input: 'openapi/api.json' -> Take the current project as the local address of the relative directory
* input: 'http://192.168.5.123:8080' -> When it does not point to the openapi file, it must be used with the `platform` parameter
*/
input: v3_1.z
.string({
required_error: 'Field input is required in `config.generator`',
})
.nonempty('Field input is required in `config.generator`'),
// Fetch options used by remote OpenAPI retrieval (headers, timeout, insecure). See FetchOptions in '@/utils/base'.
fetchOptions: exports.zFetchOptions.optional(),
/**
* A list of type identifiers to exclude from generation.
* Matches against type names parsed from the OpenAPI schema; matched types
* are skipped and referenced directly by their identifier in generated code
* to avoid duplicate or conflicting declarations.
* Use this when you already have hand-written types or types provided by
* frameworks/libraries that should not be generated.
*
* @example
* externalTypes: ['File', 'Blob', 'FormData', 'Pagination']
*/
externalTypes: v3_1.z.array(v3_1.z.string()).optional(),
/**
* Platforms that support openapi. Currently `swagger` are supported. The default is empty.
* When this parameter is specified, the input field only needs to specify the url of the document and doesn't need to be specified to the openapi file, reducing the usage threshold.
* @defualt undefined
*/
platform: exports.zPlatformType.optional(),
/**
* The output path of the interface file and type file, multiple generators cannot have repeated addresses, otherwise the generated codes will cover each other, which is meaningless.
* @requires true
*/
output: v3_1.z
.string({
required_error: 'Field output is required in `config.generator`',
})
.nonempty('Field output is required in `config.generator`'),
/**
* Specify the media type of the generated response data. After specifying, use this data type to generate the response ts format of the 2xx status code.
* @defualt 'application/json'
*/
responseMediaType: v3_1.z.string().optional(),
/**
* Specify the media type of the generated request body data. After specifying, use this data type to generate the ts format of the request body.
* @default 'application/json'
*/
bodyMediaType: v3_1.z.string().optional(),
/**
* The type of generated code. The optional value is `auto/ts/typescript/module/commonjs`.
* default is `auto`, it means the type of current project will be determined through certain rules.
*
* @param type
* 1. ts/typescript: The same meaning means generating ts type files
* 2. module: generate esModule specification file
* 3. commonjs: means generating commonjs specification file
*
* @default 'auto'
*/
type: exports.zConfigType.optional(),
/**
* Specify alova version, 2 or 3, if not specified, it will be automatically determined through the alova version in `package.json`
*/
version: v3_1.z
.union([v3_1.z.number(), v3_1.z.string()])
.optional(),
/**
* Globally exported api name, you can access the automatically generated api globally through this name.
* it is required when multiple generators are configured, and it cannot be repeated
*
* @default 'Apis'
*/
global: v3_1.z
.string()
.optional()
.refine(data => !data || loader_1.standardLoader.validate(data), data => ({
message: `\`${data}\` does not match variable specification`,
})),
/**
* The host object of global mounting, default is `globalThis`, it means `window` in browser and `global` in nodejs
*
* @default 'globalThis'
*/
globalHost: v3_1.z.string().optional(),
/**
* Whether to use `import` statement to import the type. When this option is set to `true`, the generated apiDefinitions.ts file will use `import` statement to import types instead of ///<reference types="..." />
*
* @default false
*/
useImportType: v3_1.z.boolean().optional(),
/**
* When there is no require, it defaults to require, and only nullable takes effect.
*/
defaultRequire: v3_1.z.boolean().optional(),
/**
* Control the format of output file names. Supports presets or a custom function.
*/
fileNameCase: v3_1.z
.union([
v3_1.z.enum(['camelCase', 'pascalCase', 'kebabCase', 'snakeCase']),
v3_1.z.function().args(v3_1.z.string()).returns(v3_1.z.string()),
])
.optional(),
/**
* plugin will be executed before `handleApi`
*/
plugins: v3_1.z.array(exports.zApiPlugin).optional(),
/**
* Filter or convert the generated api function and return a new `apiDescriptor` to generate the api.
* When this function is not specified, `apiDescriptor` object is not converted.
* The type of `apiDescriptor` is the same as the api item of openapi file.
*
* @see https://spec.openapis.org/oas/v3.1.0.html#operation-object
*
* @example
* ```js
* // Do not generate the apis that starts with `/user`
* handleApi(apiDescriptor) {
* if (apiDescriptor.path.startsWith('/user')) {
* return;
* }
* return apiDescriptor;
* }
* ```
*
* ```js
* // modify the api's parameters
* handleApi(apiDescriptor) {
* apiDescriptor.parameters = (apiDescriptor.parameters || []).filter(
* param => param.in === 'header' && param.name === 'token'
* );
* delete apiDescriptor.requestBody.id;
* apiDescriptor.url = apiDescriptor.url.replace('/user', '');
* return apiDescriptor;
* }
* ```
*/
handleApi: exports.zHandleApi.optional(),
});
exports.zGeneratorConfig = exports._zGeneratorConfig;
exports.zConfig = v3_1.z.object({
/**
* API generation settings are arrays. Each item represents an automatically generated rule, including the generated input and output directories, specification file addresses, etc.
* Currently, only OpenAPI specifications are supported, including OpenAPI 2.0 and 3.0 specifications.
*/
generator: v3_1.z
.array(exports.zGeneratorConfig)
.min(1, 'No items found in the `config.generator`')
.superRefine((data, ctx) => {
if (data.length < 2) {
return;
}
const globalKeySet = new Set();
const outputSet = new Set();
data.forEach((item) => {
if (outputSet.has(node_path_1.default.join(item.output ?? ''))) {
ctx.addIssue({
code: v3_1.z.ZodIssueCode.custom,
path: ['generator', 'output'],
message: `output \`${item.output}\` is repated`,
});
return;
}
outputSet.add(node_path_1.default.join(item.output ?? ''));
if (!item.global) {
ctx.addIssue({
code: v3_1.z.ZodIssueCode.custom,
path: ['generator', 'global'],
message: 'Field global is required in `config.generator`',
});
return;
}
if (globalKeySet.has(item.global)) {
ctx.addIssue({
code: v3_1.z.ZodIssueCode.custom,
path: ['generator', 'global'],
message: `global \`${item.global}\` is repated`,
});
}
globalKeySet.add(item.global);
});
}),
/**
* Whether to automatically update the interface.
* default is `true`, checked every 5 minutes, set `false` to close it
*
* @default true
*/
autoUpdate: v3_1.z
.union([
v3_1.z.boolean(),
v3_1.z
.object({
/**
* Updated when the editor is opened
*/
launchEditor: v3_1.z.boolean().optional(),
/**
* Automatic update interval in milliseconds
*/
interval: v3_1.z.number(),
})
.catch(({ input }) => input),
])
.optional()
.superRefine((data, ctx) => {
if (typeof data === 'object') {
const { interval } = data;
const time = Number(interval);
if (Number.isNaN(time)) {
ctx.addIssue({
code: v3_1.z.ZodIssueCode.custom,
path: ['autoUpdate', 'interval'],
message: 'autoUpdate.interval must be a number',
});
return;
}
if (time <= 0) {
ctx.addIssue({
code: v3_1.z.ZodIssueCode.custom,
path: ['autoUpdate', 'interval'],
message: 'Expected to set number which great than 1 in `config.autoUpdate.interval`',
});
}
}
}),
});