wormajs 0.0.2-beta.3 → 0.2.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.
Files changed (37) hide show
  1. package/dist/constant.js +7 -10
  2. package/dist/core/parser/openApiParser/helper.js +91 -69
  3. package/dist/helper/config/GeneratorHelper.js +20 -18
  4. package/dist/helper/config/zType.js +2 -2
  5. package/dist/helper/progress.js +2 -2
  6. package/dist/plugins/index.js +6 -2
  7. package/dist/plugins/presets/payloadModifier/hepler.js +29 -33
  8. package/dist/plugins/presets/payloadModifier/index.js +8 -8
  9. package/dist/plugins/presets/platform/fastapi.js +22 -0
  10. package/dist/plugins/presets/platform/index.js +15 -0
  11. package/dist/plugins/presets/platform/knife4j.js +24 -0
  12. package/dist/plugins/presets/platform/shared.js +45 -0
  13. package/dist/plugins/presets/platform/swagger.js +32 -0
  14. package/dist/plugins/presets/platform/yapi.js +75 -0
  15. package/dist/plugins/presets/rename.js +154 -21
  16. package/dist/plugins/presets/tagModifier.js +6 -2
  17. package/dist/plugins/presets/utils.js +11 -10
  18. package/dist/template/index.js +0 -34
  19. package/dist/template/presets/alova/partials/dts-extra-config.handlebars +3 -3
  20. package/dist/template/presets/alova/partials/dts-fn-declare.handlebars +1 -1
  21. package/dist/template/presets/alova/typescript/services/{tag}.ts.handlebars +5 -5
  22. package/dist/template/presets/axios/partials/dts-types.handlebars +4 -4
  23. package/dist/template/presets/axios/typescript/services/{tag}.ts.handlebars +4 -4
  24. package/dist/template/presets/config/common/worma.config.js.handlebars +1 -1
  25. package/dist/template/presets/config/module/worma.config.js.handlebars +1 -1
  26. package/dist/template/presets/config/partials/generator-content.handlebars +45 -45
  27. package/dist/template/presets/config/typescript/worma.config.ts.handlebars +1 -1
  28. package/dist/template/presets/fetch/partials/dts-types.handlebars +4 -4
  29. package/dist/template/presets/fetch/typescript/services/{tag}.ts.handlebars +4 -4
  30. package/dist/template/presets/ky/partials/dts-types.handlebars +4 -4
  31. package/dist/template/presets/ky/typescript/services/{tag}.ts.handlebars +4 -4
  32. package/dist/tsconfig.build.tsbuildinfo +1 -1
  33. package/dist/utils/template.js +23 -0
  34. package/package.json +1 -1
  35. package/typings/index.d.ts +9 -5
  36. package/typings/plugins.d.ts +134 -48
  37. package/dist/plugins/presets/platform.js +0 -73
package/dist/constant.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // 枚举常量定义 — 统一管理项目中所有硬编码的字符串/数字常量
4
4
  // ============================================================
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.FRAMEWORK_NAMES = exports.MODULE_TYPE_TO_KIND = exports.MODULE_TYPE_DIRS = exports.ModuleTypeDir = exports.PresetTemplateName = exports.TemplateSkipDir = exports.TemplatePlaceholder = exports.PluginName = exports.ModifierScope = exports.FilterScope = exports.RenameScope = exports.ParameterIn = exports.TEMPLATE_EXTENSIONS = exports.FileExtension = exports.FrameworkName = exports.ModuleKind = exports.PlatformTypeEnum = exports.ConfigTypeEnum = exports.TemplateTypeEnum = void 0;
6
+ exports.FRAMEWORK_NAMES = exports.MODULE_TYPE_TO_KIND = exports.MODULE_TYPE_DIRS = exports.ModuleTypeDir = exports.PresetTemplateName = exports.TemplateSkipDir = exports.TemplatePlaceholder = exports.PluginName = exports.FilterScope = exports.RenameScope = exports.ParameterIn = exports.TEMPLATE_EXTENSIONS = exports.FileExtension = exports.FrameworkName = exports.ModuleKind = exports.PlatformTypeEnum = exports.ConfigTypeEnum = exports.TemplateTypeEnum = void 0;
7
7
  exports.getTypeFileExtension = getTypeFileExtension;
8
8
  /** 模板代码生成类型 */
9
9
  var TemplateTypeEnum;
@@ -26,6 +26,7 @@ var PlatformTypeEnum;
26
26
  (function (PlatformTypeEnum) {
27
27
  PlatformTypeEnum["SWAGGER"] = "swagger";
28
28
  PlatformTypeEnum["KNIFE4J"] = "knife4j";
29
+ PlatformTypeEnum["FASTAPI"] = "fastapi";
29
30
  PlatformTypeEnum["YAPI"] = "yapi";
30
31
  })(PlatformTypeEnum || (exports.PlatformTypeEnum = PlatformTypeEnum = {}));
31
32
  /** 模块体系类型 */
@@ -77,6 +78,7 @@ var RenameScope;
77
78
  RenameScope["DATA"] = "data";
78
79
  RenameScope["RESPONSE"] = "response";
79
80
  RenameScope["REF_NAME"] = "refName";
81
+ RenameScope["NAME"] = "name";
80
82
  })(RenameScope || (exports.RenameScope = RenameScope = {}));
81
83
  /** 过滤 scope 类型 */
82
84
  var FilterScope;
@@ -84,14 +86,6 @@ var FilterScope;
84
86
  FilterScope["URL"] = "url";
85
87
  FilterScope["TAG"] = "tag";
86
88
  })(FilterScope || (exports.FilterScope = FilterScope = {}));
87
- /** 参数修改 scope 类型 */
88
- var ModifierScope;
89
- (function (ModifierScope) {
90
- ModifierScope["PARAMS"] = "params";
91
- ModifierScope["PATH_PARAMS"] = "pathParams";
92
- ModifierScope["DATA"] = "data";
93
- ModifierScope["RESPONSE"] = "response";
94
- })(ModifierScope || (exports.ModifierScope = ModifierScope = {}));
95
89
  /** 内建插件名称 */
96
90
  var PluginName;
97
91
  (function (PluginName) {
@@ -102,7 +96,10 @@ var PluginName;
102
96
  PluginName["IMPORT_TYPE"] = "importType";
103
97
  PluginName["AI_DOC"] = "aiDoc";
104
98
  PluginName["APIFOX"] = "apifox";
105
- PluginName["PLATFORM"] = "platform";
99
+ PluginName["SWAGGER"] = "swagger";
100
+ PluginName["KNIFE4J"] = "knife4j";
101
+ PluginName["FASTAPI"] = "fastapi";
102
+ PluginName["YAPI"] = "yapi";
106
103
  PluginName["TEMPLATE_ALOVA"] = "templateAlova";
107
104
  PluginName["TEMPLATE_ALOVA_GLOBALS"] = "templateAlovaGlobals";
108
105
  PluginName["TEMPLATE_AXIOS"] = "templateAxios";
@@ -13,7 +13,6 @@ const swagger2openapi_1 = __importDefault(require("swagger2openapi"));
13
13
  const poolManager_1 = require("../../../core/workerPool/poolManager");
14
14
  const helper_1 = require("../../../helper");
15
15
  const utils_1 = require("../../../utils");
16
- const supportedExtname = ['json', 'yaml'];
17
16
  function isSwagger2(data) {
18
17
  return !!data?.swagger;
19
18
  }
@@ -61,56 +60,72 @@ function convertSwagger2Async(data) {
61
60
  });
62
61
  });
63
62
  }
64
- // Parse local openapi files
65
- async function parseLocalFile(url, projectPath = process.cwd()) {
66
- const [, extname] = /\.([^.]+)$/.exec(url) ?? [];
67
- if (!supportedExtname.includes(extname)) {
68
- throw helper_1.logger.throwError(`Unsupported file type: ${extname}`, {
69
- url,
70
- projectPath,
71
- });
72
- }
63
+ // Read local openapi spec file as raw text
64
+ async function fetchRawLocalFile(url, projectPath = process.cwd()) {
73
65
  const filePath = node_path_1.default.resolve(projectPath, url);
74
- if (extname === 'yaml') {
75
- return js_yaml_1.default.load(await promises_1.default.readFile(filePath, 'utf-8'));
76
- }
77
- // M6-C3: prefer async read, fallback to require() for environments where
78
- // fs is mocked (e.g. memfs in tests) but the real file exists on disk
79
66
  try {
80
- return JSON.parse(await promises_1.default.readFile(filePath, 'utf-8'));
67
+ return await promises_1.default.readFile(filePath, 'utf-8');
81
68
  }
82
69
  catch {
83
- // Fallback: use require() which bypasses fs mocks to reach real disk
70
+ // M6-C3: fallback to require() for environments where fs is mocked
71
+ // (e.g. memfs in tests) but the real file exists on disk. Limited to JSON.
84
72
  // eslint-disable-next-line ts/no-require-imports
85
- return require(filePath);
73
+ return JSON.stringify(require(filePath), null, 2);
86
74
  }
87
75
  }
88
- // Parse remote openapi files
89
- async function parseRemoteFile(url, fetchOptions) {
90
- const dataText = (await (0, utils_1.fetchData)(url, fetchOptions)) ?? '';
91
- let data;
92
- try {
93
- // 尝试解析为 JSON 格式
94
- data = JSON.parse(dataText);
76
+ // Fetch remote openapi spec as raw text
77
+ async function fetchRawRemoteFile(url, fetchOptions) {
78
+ return (await (0, utils_1.fetchData)(url, fetchOptions)) ?? '';
79
+ }
80
+ const isRemoteUrl = (u) => /^https?:\/\//.test(u);
81
+ /**
82
+ * Fetch the raw spec text (JSON/YAML) from the first URL that succeeds.
83
+ * Returns the raw text together with the resolved URL; throws if all URLs fail.
84
+ */
85
+ async function fetchRawText(urls, options) {
86
+ if (urls.length === 0) {
87
+ throw helper_1.logger.throwError('No URLs provided to fetch OpenAPI document');
95
88
  }
96
- catch (jsonError) {
97
- try {
98
- // JSON 解析失败,尝试解析为 YAML 格式
99
- data = js_yaml_1.default.load(dataText);
100
- }
101
- catch (yamlError) {
102
- throw helper_1.logger.throwError(`Only JSON and YAML formats are supported. Parsing failed:
103
- ${jsonError instanceof Error ? jsonError.message : String(jsonError)}
104
- ${yamlError instanceof Error ? yamlError.message : String(yamlError)}`, {
105
- url,
106
- });
107
- }
89
+ const { projectPath, fetchOptions } = options;
90
+ // All URLs race in parallel: local files are fast, remote ones use network.
91
+ // Each task fetches the raw text AND validates that it is actually an OpenAPI/Swagger
92
+ // document before resolving. Invalid candidates (e.g. an HTML error page returned by
93
+ // a fallback URL) reject, so that Promise.any falls through to the next URL
94
+ // instead of resolving with garbage text.
95
+ const tasks = urls.map((u) => {
96
+ return (async () => {
97
+ const text = isRemoteUrl(u)
98
+ ? await fetchRawRemoteFile(u, fetchOptions)
99
+ : await fetchRawLocalFile(u, projectPath);
100
+ // Quick parse + validity check (full parse + Swagger2→OpenAPI3 conversion
101
+ // happens later in parseSpec, after beforeSpecParse may rewrite the text).
102
+ let probe;
103
+ try {
104
+ probe = JSON.parse(text);
105
+ }
106
+ catch (jsonError) {
107
+ try {
108
+ probe = js_yaml_1.default.load(text);
109
+ }
110
+ catch (yamlError) {
111
+ throw new Error(`${u}: ${(jsonError instanceof Error ? jsonError.message : String(jsonError))} (YAML: ${yamlError instanceof Error ? yamlError.message : String(yamlError)})`);
112
+ }
113
+ }
114
+ if (!isValidOpenApiData(probe)) {
115
+ throw new Error(`${u} did not yield a valid OpenAPI/Swagger document`);
116
+ }
117
+ return { text, url: u };
118
+ })();
119
+ });
120
+ try {
121
+ return await Promise.any(tasks);
108
122
  }
109
- // Validate if the data is valid (prevent server from returning error responses)
110
- if (!isValidOpenApiData(data)) {
111
- throw new Error(`Data retrieved from URL ${url} is not a valid OpenAPI document`);
123
+ catch (err) {
124
+ const errors = (err instanceof AggregateError)
125
+ ? err.errors.map((e) => e.message)
126
+ : [err.message];
127
+ throw helper_1.logger.throwError(`Unable to retrieve valid OpenAPI document from any URL:\n${errors.join('\n')}`);
112
128
  }
113
- return data;
114
129
  }
115
130
  // Validate OpenAPI data
116
131
  function isValidOpenApiData(data) {
@@ -124,49 +139,56 @@ function isValidOpenApiData(data) {
124
139
  // Check if it contains required OpenAPI/Swagger structure
125
140
  return !!(data.openapi || data.swagger || data.info || data.paths);
126
141
  }
127
- const isRemoteUrl = (u) => /^https?:\/\//.test(u);
128
142
  /**
129
- * Try all URLs in parallel (local & remote) first successful one wins.
130
- * Returns the parsed data together with the resolved URL; throws if all URLs fail.
143
+ * Parse a raw spec string (JSON or YAML) into an OpenAPIDocument.
144
+ * Performs format auto-detection, validity checks, and Swagger2→OpenAPI3 conversion.
131
145
  */
132
- async function tryUrls(urls, options) {
133
- if (urls.length === 0) {
134
- throw helper_1.logger.throwError('No URLs provided to fetch OpenAPI document');
146
+ async function parseSpec(text, url) {
147
+ let data;
148
+ try {
149
+ // Try to parse as JSON first
150
+ data = JSON.parse(text);
135
151
  }
136
- const { projectPath, fetchOptions } = options;
137
- // All URLs race in parallel: local files are fast, remote ones use network
138
- const tasks = urls.map((u) => {
139
- if (isRemoteUrl(u)) {
140
- return parseRemoteFile(u, fetchOptions).then(data => ({ data, url: u }), (err) => {
141
- throw new Error(`${u}: ${err instanceof Error ? err.message : String(err)}`);
152
+ catch (jsonError) {
153
+ try {
154
+ // Fall back to YAML (also covers JSON, since JSON is a subset of YAML)
155
+ data = js_yaml_1.default.load(text);
156
+ }
157
+ catch (yamlError) {
158
+ throw helper_1.logger.throwError(`Only JSON and YAML formats are supported. Parsing failed:
159
+ ${jsonError instanceof Error ? jsonError.message : String(jsonError)}
160
+ ${yamlError instanceof Error ? yamlError.message : String(yamlError)}`, {
161
+ url,
142
162
  });
143
163
  }
144
- return parseLocalFile(u, projectPath).then(data => ({ data, url: u }));
145
- });
146
- try {
147
- return await Promise.any(tasks);
148
164
  }
149
- catch (err) {
150
- const errors = (err instanceof AggregateError)
151
- ? err.errors.map((e) => e.message)
152
- : [err.message];
153
- throw helper_1.logger.throwError(`Unable to retrieve valid OpenAPI document from any URL:\n${errors.join('\n')}`);
165
+ // Validate if the data is valid (prevent server from returning error responses)
166
+ if (!isValidOpenApiData(data)) {
167
+ throw new Error(`Data retrieved from URL ${url} is not a valid OpenAPI document`);
168
+ }
169
+ // If it is a swagger2 file convert via worker to avoid main-thread blocking
170
+ if (isSwagger2(data)) {
171
+ data = await convertSwagger2Async(data);
154
172
  }
173
+ return data;
155
174
  }
156
175
  /**
157
176
  * Parse OpenAPI document and return the resolved URL alongside the data.
158
177
  * Use this when you need to know which URL actually provided the document.
178
+ *
179
+ * If `beforeSpecParse` is provided, it is invoked with the raw spec text once
180
+ * it has been fetched (but before parsing), and its returned string replaces
181
+ * the text that will be parsed.
159
182
  */
160
183
  async function getOpenApiDataWithUrl(url, options) {
161
- const { projectPath, fetchOptions } = options ?? {};
184
+ const { projectPath, fetchOptions, beforeSpecParse } = options ?? {};
162
185
  // Normalize to array — single string or array both handled uniformly
163
186
  const urls = Array.isArray(url) ? url : [url];
164
- const { data, url: resolvedUrl } = await tryUrls(urls, { projectPath, fetchOptions });
165
- let result = data;
166
- // If it is a swagger2 file convert via worker to avoid main-thread blocking
167
- if (isSwagger2(result)) {
168
- result = await convertSwagger2Async(result);
169
- }
187
+ const { text, url: resolvedUrl } = await fetchRawText(urls, { projectPath, fetchOptions });
188
+ // Allow the caller (e.g. a `beforeSpecParse` plugin hook) to transform the
189
+ // raw spec text before it is parsed into an OpenAPIDocument.
190
+ const finalText = (beforeSpecParse ? await beforeSpecParse(text) : text) ?? text;
191
+ const result = await parseSpec(finalText, resolvedUrl);
170
192
  if (!result) {
171
193
  throw helper_1.logger.throwError(`Cannot read file from ${urls.join(', ')}`, {
172
194
  projectPath,
@@ -128,10 +128,11 @@ class GeneratorHelper {
128
128
  fetchOptions: config.fetchOptions,
129
129
  });
130
130
  }
131
- static async openApiDataWithUrl(config, projectPath) {
131
+ static async openApiDataWithUrl(config, projectPath, opts) {
132
132
  return (0, helper_1.getOpenApiDataWithUrl)(config.input, {
133
133
  projectPath,
134
134
  fetchOptions: config.fetchOptions,
135
+ beforeSpecParse: opts?.beforeSpecParse,
135
136
  });
136
137
  }
137
138
  static async generate(config, { projectPath, force, tracker }) {
@@ -152,16 +153,17 @@ class GeneratorHelper {
152
153
  });
153
154
  reportCore(5, 'starting');
154
155
  const frozenConfig = Object.freeze(config);
155
- // Plugin: handle before parse openapi
156
- reportCore(10, 'beforeOpenapiParse');
157
- helper_2.logger.debug('Running beforeOpenapiParse hook');
158
- await pluginDriver.hookParallelEach('beforeOpenapiParse', () => ({
159
- config: frozenConfig,
160
- projectPath,
161
- }));
156
+ reportCore(10, 'beforeSpecParse');
157
+ helper_2.logger.debug('Fetching and parsing OpenAPI document', { input: config.input });
162
158
  reportCore(20, 'parsing openapi document');
163
- helper_2.logger.debug('Fetching OpenAPI document', { input: config.input });
164
- const openApiResult = await this.openApiDataWithUrl(config, projectPath);
159
+ const openApiResult = await this.openApiDataWithUrl(config, projectPath, {
160
+ // Plugin: beforeSpecParse receives the raw spec string, may return a modified string
161
+ beforeSpecParse: (spec) => pluginDriver.hookPipe('beforeSpecParse', spec, (_p, current, _ctx) => ({
162
+ config: frozenConfig,
163
+ spec: current,
164
+ projectPath,
165
+ })),
166
+ });
165
167
  let document = openApiResult.data;
166
168
  const resolvedInput = openApiResult.resolvedUrl;
167
169
  if (!document) {
@@ -174,10 +176,10 @@ class GeneratorHelper {
174
176
  version: document?.info?.version,
175
177
  paths: Object.keys(document?.paths || {}).length,
176
178
  });
177
- reportCore(35, 'openapi parsed');
178
- // Plugin: handle after parse openapi (openapiParsed)
179
- helper_2.logger.debug('Running openapiParsed hook', { pluginCount });
180
- const openapiParsed = await pluginDriver.hookSeqEach('openapiParsed', (_p, prevResult, _ctx) => {
179
+ reportCore(35, 'specParsed');
180
+ // Plugin: handle after parse openapi (specParsed)
181
+ helper_2.logger.debug('Running specParsed hook', { pluginCount });
182
+ const specParsed = await pluginDriver.hookSeqEach('specParsed', (_p, prevResult, _ctx) => {
181
183
  if (prevResult) {
182
184
  document = prevResult;
183
185
  }
@@ -187,11 +189,11 @@ class GeneratorHelper {
187
189
  projectPath,
188
190
  };
189
191
  });
190
- if (openapiParsed) {
191
- document = openapiParsed;
192
- helper_2.logger.debug('openapiParsed hook modified document');
192
+ if (specParsed) {
193
+ document = specParsed;
194
+ helper_2.logger.debug('specParsed hook modified document');
193
195
  }
194
- reportCore(45, 'openapiParsed');
196
+ reportCore(45, 'specParsed');
195
197
  const output = node_path_1.default.resolve(projectPath, config.output);
196
198
  const templateType = await GeneratorHelper.getTemplateType(config, projectPath);
197
199
  helper_2.logger.debug('Resolved output and template type', { output, templateType });
@@ -26,8 +26,8 @@ exports.zFetchOptions = v3_1.z.record(v3_1.z.string(), v3_1.z.any());
26
26
  exports.zApiPlugin = v3_1.z.object({
27
27
  name: v3_1.z.string().optional(),
28
28
  config: v3_1.z.function().optional(),
29
- beforeOpenapiParse: v3_1.z.function().optional(),
30
- openapiParsed: v3_1.z.function().optional(),
29
+ beforeSpecParse: v3_1.z.function().optional(),
30
+ specParsed: v3_1.z.function().optional(),
31
31
  beforeCodeGenerate: v3_1.z.function().optional(),
32
32
  beforeFileWrite: v3_1.z.function().optional(),
33
33
  codeGenerated: v3_1.z.function().optional(),
@@ -8,9 +8,9 @@ exports.CORE_PROGRESS_SOURCE = 'core';
8
8
  */
9
9
  exports.GeneratorStage = {
10
10
  INIT: 'init',
11
- BEFORE_OPENAPI_PARSE: 'beforeOpenapiParse',
11
+ BEFORE_SPEC_PARSE: 'beforeSpecParse',
12
12
  PARSE_OPENAPI: 'parseOpenapi',
13
- OPENAPI_PARSED: 'openapiParsed',
13
+ SPEC_PARSED: 'specParsed',
14
14
  TEMPLATE_LOADED: 'templateLoaded',
15
15
  TEMPLATE_DATA_PARSED: 'templateDataParsed',
16
16
  BEFORE_CODE_GENERATE: 'beforeCodeGenerate',
@@ -14,14 +14,18 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.ky = exports.config = exports.axios = exports.alovaGlobals = exports.alova = void 0;
17
+ exports.ky = exports.config = exports.axios = exports.alovaGlobals = exports.alova = exports.yapi = exports.swagger = exports.knife4j = exports.fastapi = void 0;
18
18
  __exportStar(require("./createPlugin"), exports);
19
19
  __exportStar(require("./presets/aiDoc"), exports);
20
20
  __exportStar(require("./presets/apifox"), exports);
21
21
  __exportStar(require("./presets/filterApi"), exports);
22
22
  __exportStar(require("./presets/importType"), exports);
23
23
  __exportStar(require("./presets/payloadModifier"), exports);
24
- __exportStar(require("./presets/platform"), exports);
24
+ var platform_1 = require("./presets/platform");
25
+ Object.defineProperty(exports, "fastapi", { enumerable: true, get: function () { return platform_1.fastapi; } });
26
+ Object.defineProperty(exports, "knife4j", { enumerable: true, get: function () { return platform_1.knife4j; } });
27
+ Object.defineProperty(exports, "swagger", { enumerable: true, get: function () { return platform_1.swagger; } });
28
+ Object.defineProperty(exports, "yapi", { enumerable: true, get: function () { return platform_1.yapi; } });
25
29
  __exportStar(require("./presets/rename"), exports);
26
30
  __exportStar(require("./presets/tagModifier"), exports);
27
31
  // Template preset plugins
@@ -15,11 +15,13 @@ function toSchemaObject(base, s) {
15
15
  delete schema.required;
16
16
  return schema;
17
17
  };
18
- // Legacy union as array (treated as oneOf)
18
+ // Native array type (elements are Schema)
19
19
  if (Array.isArray(s)) {
20
- const baseOneOf = base.oneOf || [];
20
+ const arr = s;
21
21
  cleanType(result);
22
- result.oneOf = s.map((item, idx) => toSchemaObject(baseOneOf[idx] || {}, item));
22
+ result.type = 'array';
23
+ const items = arr.map(item => toSchemaObject({}, item));
24
+ result.items = (items.length === 1 ? items[0] : items);
23
25
  return result;
24
26
  }
25
27
  // Primitive types and no-op primitives
@@ -33,18 +35,21 @@ function toSchemaObject(base, s) {
33
35
  const baseOneOf = base.oneOf || [];
34
36
  cleanType(result);
35
37
  result.oneOf = spec.oneOf.map((item, idx) => toSchemaObject(baseOneOf[idx] || {}, item));
38
+ return result;
36
39
  }
37
40
  if (s.anyOf) {
38
41
  const spec = s;
39
42
  const baseAnyOf = base.anyOf || [];
40
43
  cleanType(result);
41
44
  result.anyOf = spec.anyOf.map((item, idx) => toSchemaObject(baseAnyOf[idx] || {}, item));
45
+ return result;
42
46
  }
43
47
  if (s.allOf) {
44
48
  const spec = s;
45
49
  const baseAllOf = base.allOf || [];
46
50
  cleanType(result);
47
51
  result.allOf = spec.allOf.map((item, idx) => toSchemaObject(baseAllOf[idx] || {}, item));
52
+ return result;
48
53
  }
49
54
  // Enum: set enum and optional type
50
55
  if (s.enum) {
@@ -53,30 +58,15 @@ function toSchemaObject(base, s) {
53
58
  if (spec.type) {
54
59
  result.type = spec.type;
55
60
  }
56
- }
57
- // Array: set/merge items
58
- if (s.type === 'array') {
59
- const spec = s;
60
- result.type = 'array';
61
- const baseItems = result.items;
62
- if (Array.isArray(spec.items)) {
63
- // Tuple items: replace entire items with tuple
64
- const items = spec.items.map(item => toSchemaObject({}, item));
65
- result.items = items;
66
- }
67
- else {
68
- // Single items: merge into existing items schema
69
- const patchItem = toSchemaObject(typeof baseItems === 'object' ? baseItems : {}, spec.items);
70
- result.items = patchItem;
71
- }
72
61
  return result;
73
62
  }
74
- // Object (reference-like map): merge properties and required
63
+ // Object (reference-like map): replace properties and required with handler's spec
64
+ // (the SchemaReference returned by the handler fully replaces this field, only keeping scalar fields like description from base)
75
65
  const ref = s;
76
66
  if (ref && typeof ref === 'object') {
77
67
  result.type = 'object';
78
- const properties = { ...result.properties };
79
- const requiredSet = new Set(Array.isArray(result.required) ? result.required : []);
68
+ const properties = {};
69
+ const requiredSet = new Set();
80
70
  for (const key in ref) {
81
71
  const val = ref[key];
82
72
  if (!val) {
@@ -137,16 +127,16 @@ function toSchemaSpec(obj) {
137
127
  const type = typeof obj.type === 'string' ? obj.type : undefined;
138
128
  return { enum: obj.enum, type };
139
129
  }
140
- // Array
130
+ // Array -> native array
141
131
  if (obj.type === 'array' || obj.items) {
142
132
  const items = obj.items;
143
133
  if (Array.isArray(items)) {
144
- return { type: 'array', items: items.map((it) => toSchemaSpec(it)) };
134
+ return items.map((it) => toSchemaSpec(it));
145
135
  }
146
136
  if (items) {
147
- return { type: 'array', items: toSchemaSpec(items) };
137
+ return [toSchemaSpec(items)];
148
138
  }
149
- return { type: 'array', items: 'unknown' };
139
+ return ['unknown'];
150
140
  }
151
141
  // Object
152
142
  if (obj.type === 'object' || obj.properties) {
@@ -160,7 +150,7 @@ function toSchemaSpec(obj) {
160
150
  }
161
151
  return result;
162
152
  }
163
- // type union as array
153
+ // type union as array -> oneOf
164
154
  if (Array.isArray(obj.type)) {
165
155
  const typeArr = obj.type;
166
156
  const mapped = typeArr.map(schemaTypeToPrimitiveType);
@@ -175,21 +165,27 @@ function applyModifierSchema(schema, config, { required }) {
175
165
  }
176
166
  const cloned = { ...schema };
177
167
  const currentSpec = toSchemaSpec(cloned);
178
- const ret = config.handler(currentSpec);
168
+ // When the field is itself optional and is a primitive, wrap it as { required, type } before passing to handler
169
+ const handlerInput = (required === false && typeof currentSpec === 'string')
170
+ ? { required: false, type: currentSpec }
171
+ : currentSpec;
172
+ const ret = config.handler(handlerInput);
179
173
  if (!ret) {
180
174
  return {
181
175
  required,
182
- value: null,
176
+ schema: null,
183
177
  };
184
178
  }
185
- if (typeof ret === 'object' && 'required' in ret && 'value' in ret) {
179
+ // A returned { required, type } means changing requiredness (driven by the `type` field)
180
+ if (typeof ret === 'object' && !Array.isArray(ret) && 'required' in ret && 'type' in ret) {
181
+ const opt = ret;
186
182
  return {
187
- required: !!(ret.required ?? required),
188
- value: toSchemaObject(cloned, ret.value),
183
+ required: !!(opt.required ?? required),
184
+ schema: toSchemaObject(cloned, opt.type),
189
185
  };
190
186
  }
191
187
  return {
192
188
  required,
193
- value: toSchemaObject(cloned, ret),
189
+ schema: toSchemaObject(cloned, ret),
194
190
  };
195
191
  }
@@ -28,13 +28,13 @@ function modifySchemaProperties(schema, config) {
28
28
  if (!(0, utils_1.isMatch)(key, config.match)) {
29
29
  continue;
30
30
  }
31
- const { required: requiredOverride, value: valueSchema } = (0, hepler_1.applyModifierSchema)(props[key], config, { required: required.includes(key) });
31
+ const { required: requiredOverride, schema: schemaValue } = (0, hepler_1.applyModifierSchema)(props[key], config, { required: required.includes(key) });
32
32
  required = required.filter(r => r !== key);
33
- if (!valueSchema) {
33
+ if (!schemaValue) {
34
34
  delete props[key];
35
35
  continue;
36
36
  }
37
- props[key] = valueSchema;
37
+ props[key] = schemaValue;
38
38
  if (requiredOverride) {
39
39
  required.push(key);
40
40
  }
@@ -53,7 +53,7 @@ function modifyParameters(parameters, type, config) {
53
53
  if (!(0, utils_1.isMatch)(param.name, config.match)) {
54
54
  return param;
55
55
  }
56
- const { value: schema, required } = (0, hepler_1.applyModifierSchema)(param.schema, config, { required: !!param.required });
56
+ const { schema, required } = (0, hepler_1.applyModifierSchema)(param.schema, config, { required: !!param.required });
57
57
  if (!schema) {
58
58
  return null;
59
59
  }
@@ -73,22 +73,22 @@ function payloadModifierApiDescriptor(apiDescriptor, config) {
73
73
  const newDescriptor = { ...apiDescriptor };
74
74
  const { scope } = config;
75
75
  switch (scope) {
76
- case constant_1.ModifierScope.PARAMS:
76
+ case 'params':
77
77
  if (newDescriptor.parameters) {
78
78
  newDescriptor.parameters = modifyParameters(newDescriptor.parameters, constant_1.ParameterIn.QUERY, config);
79
79
  }
80
80
  break;
81
- case constant_1.ModifierScope.PATH_PARAMS:
81
+ case 'pathParams':
82
82
  if (newDescriptor.parameters) {
83
83
  newDescriptor.parameters = modifyParameters(newDescriptor.parameters, constant_1.ParameterIn.PATH, config);
84
84
  }
85
85
  break;
86
- case constant_1.ModifierScope.DATA:
86
+ case 'data':
87
87
  if (newDescriptor.requestBody) {
88
88
  newDescriptor.requestBody = modifySchemaProperties(newDescriptor.requestBody, config);
89
89
  }
90
90
  break;
91
- case constant_1.ModifierScope.RESPONSE:
91
+ case 'response':
92
92
  if (newDescriptor.responses) {
93
93
  newDescriptor.responses = modifySchemaProperties(newDescriptor.responses, config);
94
94
  }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fastapi = void 0;
4
+ const constant_1 = require("../../../constant");
5
+ const shared_1 = require("./shared");
6
+ /**
7
+ * FastAPI platform plugin.
8
+ *
9
+ * Pass the base URL of your FastAPI app; the plugin will try `/openapi.json`
10
+ * first, then fall back to the bare base URL.
11
+ *
12
+ * @param input - base URL string, or an array of base URLs
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * plugins: [fastapi('http://fastapi-example.dokkuapp.com'), alovaGlobals()]
17
+ * ```
18
+ */
19
+ exports.fastapi = (0, shared_1.defineUrlPlatformPlugin)(constant_1.PluginName.FASTAPI, base => [
20
+ `${base}/openapi.json`,
21
+ base,
22
+ ]);
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.yapi = exports.swagger = exports.withCookie = exports.normalizeBase = exports.defineUrlPlatformPlugin = exports.knife4j = exports.fastapi = void 0;
4
+ var fastapi_1 = require("./fastapi");
5
+ Object.defineProperty(exports, "fastapi", { enumerable: true, get: function () { return fastapi_1.fastapi; } });
6
+ var knife4j_1 = require("./knife4j");
7
+ Object.defineProperty(exports, "knife4j", { enumerable: true, get: function () { return knife4j_1.knife4j; } });
8
+ var shared_1 = require("./shared");
9
+ Object.defineProperty(exports, "defineUrlPlatformPlugin", { enumerable: true, get: function () { return shared_1.defineUrlPlatformPlugin; } });
10
+ Object.defineProperty(exports, "normalizeBase", { enumerable: true, get: function () { return shared_1.normalizeBase; } });
11
+ Object.defineProperty(exports, "withCookie", { enumerable: true, get: function () { return shared_1.withCookie; } });
12
+ var swagger_1 = require("./swagger");
13
+ Object.defineProperty(exports, "swagger", { enumerable: true, get: function () { return swagger_1.swagger; } });
14
+ var yapi_1 = require("./yapi");
15
+ Object.defineProperty(exports, "yapi", { enumerable: true, get: function () { return yapi_1.yapi; } });
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.knife4j = void 0;
4
+ const constant_1 = require("../../../constant");
5
+ const shared_1 = require("./shared");
6
+ /**
7
+ * Knife4j platform plugin.
8
+ *
9
+ * Pass the base URL of your Knife4j instance; the plugin will try the OAS3
10
+ * endpoint (springdoc) first, then the Swagger2 endpoint (springfox), then the
11
+ * bare base URL.
12
+ *
13
+ * @param input - base URL string, or an array of base URLs
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * plugins: [knife4j('https://openapi3.demo.knife4jnext.com'), alovaGlobals()]
18
+ * ```
19
+ */
20
+ exports.knife4j = (0, shared_1.defineUrlPlatformPlugin)(constant_1.PluginName.KNIFE4J, base => [
21
+ `${base}/v3/api-docs`,
22
+ `${base}/v2/api-docs`,
23
+ base,
24
+ ]);