@nestia/migrate 13.0.4 → 14.0.1
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 +1 -1
- package/lib/analyzers/NestiaMigrateControllerAnalyzer.d.ts +13 -0
- package/lib/analyzers/NestiaMigrateControllerAnalyzer.js +32 -2
- package/lib/analyzers/NestiaMigrateControllerAnalyzer.js.map +1 -1
- package/lib/analyzers/NestiaMigrateControllerAnalyzer.mjs +14 -2
- package/lib/bundles/NEST_TEMPLATE.js +1 -1
- package/lib/bundles/NEST_TEMPLATE.js.map +1 -1
- package/lib/bundles/NEST_TEMPLATE.mjs +1 -1
- package/lib/bundles/SDK_TEMPLATE.js +1 -1
- package/lib/bundles/SDK_TEMPLATE.js.map +1 -1
- package/lib/bundles/SDK_TEMPLATE.mjs +1 -1
- package/lib/executable/NestiaMigrateCommander.js +5 -1
- package/lib/executable/NestiaMigrateCommander.js.map +1 -1
- package/lib/executable/NestiaMigrateCommander.mjs +1 -1
- package/lib/executable/NestiaMigrateInquirer.js +7 -25
- package/lib/executable/NestiaMigrateInquirer.js.map +1 -1
- package/lib/executable/NestiaMigrateInquirer.mjs +5 -7
- package/lib/programmers/NestiaMigrateApiFunctionProgrammer.js +7 -5
- package/lib/programmers/NestiaMigrateApiFunctionProgrammer.js.map +1 -1
- package/lib/programmers/NestiaMigrateApiFunctionProgrammer.mjs +4 -2
- package/lib/programmers/NestiaMigrateApiNamespaceProgrammer.js +31 -13
- package/lib/programmers/NestiaMigrateApiNamespaceProgrammer.js.map +1 -1
- package/lib/programmers/NestiaMigrateApiNamespaceProgrammer.mjs +17 -8
- package/lib/programmers/NestiaMigrateApiSimulationProgrammer.js +24 -22
- package/lib/programmers/NestiaMigrateApiSimulationProgrammer.js.map +1 -1
- package/lib/programmers/NestiaMigrateApiSimulationProgrammer.mjs +13 -20
- package/lib/programmers/NestiaMigrateE2eFileProgrammer.js +20 -5
- package/lib/programmers/NestiaMigrateE2eFileProgrammer.js.map +1 -1
- package/lib/programmers/NestiaMigrateE2eFileProgrammer.mjs +6 -2
- package/lib/programmers/NestiaMigrateNestMethodProgrammer.js +69 -13
- package/lib/programmers/NestiaMigrateNestMethodProgrammer.js.map +1 -1
- package/lib/programmers/NestiaMigrateNestMethodProgrammer.mjs +46 -9
- package/lib/programmers/NestiaMigrateSchemaProgrammer.js +25 -4
- package/lib/programmers/NestiaMigrateSchemaProgrammer.js.map +1 -1
- package/lib/programmers/NestiaMigrateSchemaProgrammer.mjs +15 -4
- package/lib/utils/PathTemplate.d.ts +19 -0
- package/lib/utils/PathTemplate.js +42 -0
- package/lib/utils/PathTemplate.js.map +1 -0
- package/lib/utils/PathTemplate.mjs +34 -0
- package/lib/utils/PathTemplate.mjs.map +1 -0
- package/lib/utils/RequestBody.d.ts +9 -0
- package/lib/utils/RequestBody.js +16 -0
- package/lib/utils/RequestBody.js.map +1 -0
- package/lib/utils/RequestBody.mjs +9 -0
- package/lib/utils/RequestBody.mjs.map +1 -0
- package/lib/utils/SuccessStatus.d.ts +14 -0
- package/lib/utils/SuccessStatus.js +28 -0
- package/lib/utils/SuccessStatus.js.map +1 -0
- package/lib/utils/SuccessStatus.mjs +14 -0
- package/lib/utils/SuccessStatus.mjs.map +1 -0
- package/package.json +4 -4
- package/src/analyzers/NestiaMigrateControllerAnalyzer.ts +39 -2
- package/src/bundles/NEST_TEMPLATE.ts +1 -1
- package/src/bundles/SDK_TEMPLATE.ts +1 -1
- package/src/executable/NestiaMigrateCommander.ts +7 -1
- package/src/executable/NestiaMigrateInquirer.ts +12 -26
- package/src/programmers/NestiaMigrateApiFunctionProgrammer.ts +10 -4
- package/src/programmers/NestiaMigrateApiNamespaceProgrammer.ts +41 -20
- package/src/programmers/NestiaMigrateApiSimulationProgrammer.ts +23 -73
- package/src/programmers/NestiaMigrateE2eFileProgrammer.ts +41 -8
- package/src/programmers/NestiaMigrateNestMethodProgrammer.ts +88 -29
- package/src/programmers/NestiaMigrateSchemaProgrammer.ts +28 -10
- package/src/utils/PathTemplate.ts +39 -0
- package/src/utils/RequestBody.ts +14 -0
- package/src/utils/SuccessStatus.ts +29 -0
|
@@ -5,7 +5,7 @@ export const SDK_TEMPLATE: Record<string, string> = {
|
|
|
5
5
|
"LICENSE": "MIT License\n\nCopyright (c) 2024 Jeongho Nam\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
|
|
6
6
|
"README.md": "# Software Development Kit\nThis is a SDK library generated by [`@nestia/migrate`](https://nestia.io/docs/migrate) or [`@nestia/editor`](https://nestia.io/docs/editor).\n\nWith this SDK library, you can easily and safely interact with backend server.\n\nJust import and call some API functions like gif image below:\n\n\n\n> Left is server code, and right is client code utilizing the SDK\n\n\n\n\n## How to Test\n```bash\npnpm install\npnpm run start # run only \"test/start.ts\" file\npnpm run test # everything under the \"test/features\" directory\npnpm run test:simulate # \"test/features\" with mockup simulation mode\n```\n\nIf you run `pnpm run start` command, only [test/start.ts](test/start.ts) file would be executed.\n\nOtherwise you run `pnpm run test` command instead, run everything in the [test/features](test/features) directory.\n\nFor reference, the [test/features](test/features) directory and E2E test functions (for each API endpoints) would be automatically composed only when you've configured the \"E2E test function generation mode\" of the `@nestia/migrate` (or `@nestia/editor`).\n\n```bash\npnpm dlx @nestia/migrate\n? Migration mode (Use arrow keys):\n NestJS\n > SDK\n? Swagger file location: assets/input/clickhouse.json\n? Output directory path: assets/output/clickhouse-sdk-manual\n? Mokup Simulator: true\n? E2E Test Functions: true\n```\n\n\n\n\n## Deploy\n```bash\npnpm install\npnpm run deploy\n```\n\nJust run `pnpm run deploy` command, then your SDK library would be published.\n\nBy the way, the initial package name of this template repository is `@ORGANIZATION/PROJECT-api`. I think it would better to change the word to your own organization and project name. If you're utilizing `VsCode`, you can do it through `Edit > Replace in Files` (*Ctrl + Shift + H*) feature.\n\n-----------\n\n> ## What [`Nestia`](https://nestia.io) is:\n> \n> \n> [](https://github.com/samchon/nestia/blob/master/LICENSE)\n> [](https://www.npmjs.com/package/@nestia/core)\n> [](https://www.npmjs.com/package/@nestia/core)\n> [](https://github.com/samchon/nestia/actions?query=workflow%3Abuild)\n> [](https://nestia.io/docs/)\n> \n> Nestia is a set of helper libraries for NestJS, supporting below features:\n> \n> - `@nestia/core`: Super-fast decorators\n> - `@nestia/sdk`:\n> - Swagger generator evolved than ever\n> - SDK library generator for clients\n> - Mockup Simulator for client applications\n> - Automatic E2E test functions generator\n> - `@nestia/migrate`: Migration from Swagger to NestJS\n> - `@nestia/editor`: Online TypeScript Swagger Editor\n> - `nestia`: Just CLI (command line interface) tool\n> \n>> **Note**\n>> \n>> - **Only one line** required, with pure TypeScript type\n>> - Enhance performance **30x** up\n>> - Runtime validator is **20,000x faster** than `class-validator`\n>> - JSON serialization is **200x faster** than `class-transformer`\n>> - Software Development Kit\n>> - SDK is a collection of `fetch` functions with type definitions like [tRPC](https://> trpc.io/)\n>> - Mockup simulator means embedded backend simulator in SDK\n>> - similar with [msw](https://mswjs.io/), but fully automated\n",
|
|
7
7
|
"hello.js": "function print(command, description) {\n return console.log(`\\x1b[1m${command}\\x1b[2m: ${description}\\x1b[0m`);\n}\n\nconsole.log(\"-----------------------------------------\");\nconsole.log(\"\\x1b[7mGenerated by \\x1b[2m@nestia/editor\\x1b[0m\");\nconsole.log(\"\");\nconsole.log(\" - \\x1b[36mhttps://nestia.io/docs/editor\\x1b[0m\");\nconsole.log(\" - \\x1b[36mhttps://github.com/samchon/nestia\\x1b[0m\");\nconsole.log(\"-----------------------------------------\");\n\nprint(\"pnpm run start\", \"Run only test/start.ts\");\nprint(\"pnpm run test\", \"Run every test/features/**/*.ts files\");\nprint(\"pnpm run test:simulate\", \"Test with mockup simulator\");\n",
|
|
8
|
-
"package.json": "{\n \"name\": \"@ORGANIZATION/PROJECT-api\",\n \"version\": \"0.1.0\",\n \"description\": \"SDK library generated by Nestia\",\n \"main\": \"src/index.ts\",\n \"exports\": {\n \".\": \"./src/index.ts\",\n \"./lib/*\": \"./src/*.ts\",\n \"./package.json\": \"./package.json\"\n },\n \"publishConfig\": {\n \"main\": \"lib/index.mjs\",\n \"module\": \"lib/index.mjs\",\n \"types\": \"lib/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./lib/index.d.ts\",\n \"import\": \"./lib/index.mjs\",\n \"default\": \"./lib/index.js\"\n },\n \"./lib/*\": {\n \"types\": \"./lib/*.d.ts\",\n \"import\": \"./lib/*.mjs\",\n \"default\": \"./lib/*.js\"\n },\n \"./package.json\": \"./package.json\"\n }\n },\n \"scripts\": {\n \"build\": \"rimraf lib && ttsc && rolldown -c\",\n \"build:test\": \"rimraf bin && ttsc --project test/tsconfig.json\",\n \"deploy\": \"pnpm run build && pnpm publish\",\n \"dev\": \"pnpm run build:test --watch\",\n \"hello\": \"node hello\",\n \"start\": \"ttsx test/start.ts\",\n \"swagger\": \"ttsx test/swagger.ts\",\n \"test\": \"ttsx test/index.ts\",\n \"test:simulate\": \"ttsx test/index.ts --simulate true\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/samchon/nestia\"\n },\n \"author\": \"Jeongho Nam\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/samchon/nestia/issues\"\n },\n \"homepage\": \"https://nestia.io\",\n \"files\": [\n \"lib\",\n \"swagger.json\",\n \"package.json\",\n \"README.md\"\n ],\n \"dependencies\": {\n \"@nestia/fetcher\": \"^
|
|
8
|
+
"package.json": "{\n \"name\": \"@ORGANIZATION/PROJECT-api\",\n \"version\": \"0.1.0\",\n \"description\": \"SDK library generated by Nestia\",\n \"main\": \"src/index.ts\",\n \"exports\": {\n \".\": \"./src/index.ts\",\n \"./lib/*\": \"./src/*.ts\",\n \"./package.json\": \"./package.json\"\n },\n \"publishConfig\": {\n \"main\": \"lib/index.mjs\",\n \"module\": \"lib/index.mjs\",\n \"types\": \"lib/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./lib/index.d.ts\",\n \"import\": \"./lib/index.mjs\",\n \"default\": \"./lib/index.js\"\n },\n \"./lib/*\": {\n \"types\": \"./lib/*.d.ts\",\n \"import\": \"./lib/*.mjs\",\n \"default\": \"./lib/*.js\"\n },\n \"./package.json\": \"./package.json\"\n }\n },\n \"scripts\": {\n \"build\": \"rimraf lib && ttsc && rolldown -c\",\n \"build:test\": \"rimraf bin && ttsc --project test/tsconfig.json\",\n \"deploy\": \"pnpm run build && pnpm publish\",\n \"dev\": \"pnpm run build:test --watch\",\n \"hello\": \"node hello\",\n \"start\": \"ttsx test/start.ts\",\n \"swagger\": \"ttsx test/swagger.ts\",\n \"test\": \"ttsx test/index.ts\",\n \"test:simulate\": \"ttsx test/index.ts --simulate true\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/samchon/nestia\"\n },\n \"author\": \"Jeongho Nam\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/samchon/nestia/issues\"\n },\n \"homepage\": \"https://nestia.io\",\n \"files\": [\n \"lib\",\n \"swagger.json\",\n \"package.json\",\n \"README.md\"\n ],\n \"dependencies\": {\n \"@nestia/fetcher\": \"^14.0.1\",\n \"tgrid\": \"^1.2.1\",\n \"typia\": \"15.0.0\"\n },\n \"devDependencies\": {\n \"@nestia/e2e\": \"^14.0.1\",\n \"@ttsc/unplugin\": \"^0.28.1\",\n \"@types/express\": \"^4.17.21\",\n \"@types/swagger-ui-express\": \"^4.1.6\",\n \"chalk\": \"4.1.2\",\n \"express\": \"^4.19.2\",\n \"rimraf\": \"^5.0.5\",\n \"rolldown\": \"^1.1.5\",\n \"swagger-ui-express\": \"^5.0.0\",\n \"tinyglobby\": \"^0.2.17\",\n \"ttsc\": \"^0.28.1\",\n \"typescript\": \"^7.0.2\",\n \"@nestia/core\": \"^14.0.1\"\n },\n \"packageManager\": \"pnpm@10.19.0\"\n}",
|
|
9
9
|
"rolldown.config.mjs": "import ttsc from \"@ttsc/unplugin/rolldown\";\nimport path from \"node:path\";\nimport { globSync } from \"tinyglobby\";\n\nexport default {\n input: globSync(\"./src/**/*.ts\"),\n external: (id) => !id.startsWith(\".\") && !path.isAbsolute(id),\n output: {\n dir: \"./lib\",\n format: \"esm\",\n sourcemap: true,\n entryFileNames: \"[name].mjs\",\n preserveModules: true,\n preserveModulesRoot: \"src\",\n },\n plugins: [ttsc()],\n};\n",
|
|
10
10
|
"src/HttpError.ts": "export { HttpError } from \"@nestia/fetcher\";\n",
|
|
11
11
|
"src/IConnection.ts": "export type { IConnection } from \"@nestia/fetcher\";\n",
|
|
@@ -76,8 +76,14 @@ export namespace NestiaMigrateCommander {
|
|
|
76
76
|
);
|
|
77
77
|
await NestiaMigrateFileArchiver.archive({
|
|
78
78
|
mkdir: fs.promises.mkdir,
|
|
79
|
+
// only TypeScript is formatted: prettier's TypeScript parser also
|
|
80
|
+
// accepts a `.gitignore` or `.env` line, and rewrites it into code
|
|
79
81
|
writeFile: async (file, content) =>
|
|
80
|
-
fs.promises.writeFile(
|
|
82
|
+
fs.promises.writeFile(
|
|
83
|
+
file,
|
|
84
|
+
/\.[cm]?tsx?$/.test(file) ? await beautify(content) : content,
|
|
85
|
+
"utf-8",
|
|
86
|
+
),
|
|
81
87
|
root: options.output,
|
|
82
88
|
files,
|
|
83
89
|
});
|
|
@@ -74,32 +74,18 @@ export namespace NestiaMigrateInquirer {
|
|
|
74
74
|
partial.input ??= await input("input")("Swagger file location");
|
|
75
75
|
partial.output ??= await input("output")("Response directory path");
|
|
76
76
|
partial.package ??= await input("package")("Package name");
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
])) === "true";
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
partial.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"false",
|
|
90
|
-
])) === "true";
|
|
91
|
-
if (partial.simulate)
|
|
92
|
-
partial.simulate = (partial.simulate as any) === "true";
|
|
93
|
-
else
|
|
94
|
-
partial.simulate =
|
|
95
|
-
(await select("simulate")("Mokup Simulator")(["true", "false"])) ===
|
|
96
|
-
"true";
|
|
97
|
-
|
|
98
|
-
if (partial.e2e) partial.e2e = (partial.e2e as any) === "true";
|
|
99
|
-
else
|
|
100
|
-
partial.e2e =
|
|
101
|
-
(await select("e2e")("Generate E2E tests")(["true", "false"])) ===
|
|
102
|
-
"true";
|
|
77
|
+
// a flag given alone is `true`, and one given a value is its text
|
|
78
|
+
const flag = (value: unknown): boolean | undefined =>
|
|
79
|
+
value === undefined ? undefined : value === true || value === "true";
|
|
80
|
+
const ask = async (name: string, message: string): Promise<boolean> =>
|
|
81
|
+
(await select(name)(message)(["true", "false"])) === "true";
|
|
82
|
+
partial.keyword =
|
|
83
|
+
flag(partial.keyword) ??
|
|
84
|
+
(await ask("keyword", "Keyword parameter mode"));
|
|
85
|
+
partial.simulate =
|
|
86
|
+
flag(partial.simulate) ?? (await ask("simulate", "Mokup Simulator"));
|
|
87
|
+
partial.e2e =
|
|
88
|
+
flag(partial.e2e) ?? (await ask("e2e", "Generate E2E tests"));
|
|
103
89
|
return partial as IOutput;
|
|
104
90
|
});
|
|
105
91
|
};
|
|
@@ -7,6 +7,8 @@ import { StatementFactory } from "../factories/StatementFactory";
|
|
|
7
7
|
import ts from "../internal/ts";
|
|
8
8
|
import { INestiaMigrateConfig } from "../structures/INestiaMigrateConfig";
|
|
9
9
|
import { FilePrinter } from "../utils/FilePrinter";
|
|
10
|
+
import { RequestBody } from "../utils/RequestBody";
|
|
11
|
+
import { SuccessStatus } from "../utils/SuccessStatus";
|
|
10
12
|
import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
|
|
11
13
|
import { NestiaMigrateSchemaProgrammer } from "./NestiaMigrateSchemaProgrammer";
|
|
12
14
|
|
|
@@ -110,9 +112,7 @@ export namespace NestiaMigrateApiFunctionProgrammer {
|
|
|
110
112
|
factory.createTypeReferenceNode(
|
|
111
113
|
`${ctx.route.accessor.at(-1)!}.Body`,
|
|
112
114
|
),
|
|
113
|
-
(ctx.route
|
|
114
|
-
ctx.route.body.type === "text/plain") &&
|
|
115
|
-
ctx.route.operation().requestBody?.required === false
|
|
115
|
+
RequestBody.optional(ctx.route)
|
|
116
116
|
? factory.createToken(SyntaxKind.QuestionToken)
|
|
117
117
|
: undefined,
|
|
118
118
|
),
|
|
@@ -208,9 +208,15 @@ export namespace NestiaMigrateApiFunctionProgrammer {
|
|
|
208
208
|
getArguments(ctx, false),
|
|
209
209
|
),
|
|
210
210
|
),
|
|
211
|
+
// the fetcher reads only 200, 201, and this status as success
|
|
211
212
|
factory.createPropertyAssignment(
|
|
212
213
|
"status",
|
|
213
|
-
|
|
214
|
+
((status) =>
|
|
215
|
+
status === null
|
|
216
|
+
? factory.createNull()
|
|
217
|
+
: factory.createNumericLiteral(status))(
|
|
218
|
+
SuccessStatus.of(ctx.route),
|
|
219
|
+
),
|
|
214
220
|
),
|
|
215
221
|
],
|
|
216
222
|
true,
|
|
@@ -9,6 +9,8 @@ import { TypeFactory } from "../factories/TypeFactory";
|
|
|
9
9
|
import ts from "../internal/ts";
|
|
10
10
|
import { INestiaMigrateConfig } from "../structures/INestiaMigrateConfig";
|
|
11
11
|
import { FilePrinter } from "../utils/FilePrinter";
|
|
12
|
+
import { PathTemplate } from "../utils/PathTemplate";
|
|
13
|
+
import { RequestBody } from "../utils/RequestBody";
|
|
12
14
|
import { NestiaMigrateApiSimulationProgrammer } from "./NestiaMigrateApiSimulationProgrammer";
|
|
13
15
|
import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
|
|
14
16
|
import { NestiaMigrateSchemaProgrammer } from "./NestiaMigrateSchemaProgrammer";
|
|
@@ -116,7 +118,9 @@ export namespace NestiaMigrateApiNamespaceProgrammer {
|
|
|
116
118
|
required: [
|
|
117
119
|
...ctx.route.parameters.map((p) => p.key),
|
|
118
120
|
...(ctx.route.query ? [ctx.route.query.key] : []),
|
|
119
|
-
...(ctx.route.body
|
|
121
|
+
...(ctx.route.body && RequestBody.optional(ctx.route) === false
|
|
122
|
+
? [ctx.route.body.key]
|
|
123
|
+
: []),
|
|
120
124
|
],
|
|
121
125
|
},
|
|
122
126
|
}),
|
|
@@ -280,32 +284,49 @@ export namespace NestiaMigrateApiNamespaceProgrammer {
|
|
|
280
284
|
),
|
|
281
285
|
);
|
|
282
286
|
const template = () => {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
287
|
+
// the document's template delimits each parameter; the emended path's
|
|
288
|
+
// `:id.json` would not tell the name from the literal after it
|
|
289
|
+
const segments: PathTemplate.ISegment[] = PathTemplate.segments(
|
|
290
|
+
ctx.route,
|
|
291
|
+
);
|
|
292
|
+
if (segments.every((segment) => segment.type === "literal"))
|
|
293
|
+
return factory.createStringLiteral(getPath(ctx.route));
|
|
294
|
+
const literals: string[] = [""];
|
|
295
|
+
const parameters: IHttpMigrateRoute.IParameter[] = [];
|
|
296
|
+
for (const segment of segments)
|
|
297
|
+
if (segment.type === "literal")
|
|
298
|
+
literals[literals.length - 1] += segment.value;
|
|
299
|
+
else {
|
|
300
|
+
parameters.push(segment.parameter);
|
|
301
|
+
literals.push("");
|
|
302
|
+
}
|
|
286
303
|
return factory.createTemplateExpression(
|
|
287
|
-
factory.createTemplateHead(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
304
|
+
factory.createTemplateHead(literals[0]!),
|
|
305
|
+
parameters.map((parameter, i) =>
|
|
306
|
+
factory.createTemplateSpan(
|
|
307
|
+
// URI-encoded, "null" when nullish, and a dot segment refused
|
|
291
308
|
factory.createCallExpression(
|
|
292
|
-
|
|
309
|
+
IdentifierFactory.access(
|
|
310
|
+
factory.createIdentifier(
|
|
311
|
+
ctx.importer.external({
|
|
312
|
+
type: "instance",
|
|
313
|
+
library: "@nestia/fetcher",
|
|
314
|
+
name: "PathParameter",
|
|
315
|
+
}),
|
|
316
|
+
),
|
|
317
|
+
"encode",
|
|
318
|
+
),
|
|
293
319
|
undefined,
|
|
294
320
|
[
|
|
295
|
-
factory.
|
|
296
|
-
|
|
297
|
-
ctx.route.parameters.find((p) => p.name === name)!.key,
|
|
298
|
-
),
|
|
299
|
-
factory.createToken(SyntaxKind.QuestionQuestionToken),
|
|
300
|
-
factory.createStringLiteral("null"),
|
|
301
|
-
),
|
|
321
|
+
factory.createStringLiteral(parameter.key),
|
|
322
|
+
property(parameter.key),
|
|
302
323
|
],
|
|
303
324
|
),
|
|
304
|
-
(i !==
|
|
325
|
+
(i !== parameters.length - 1
|
|
305
326
|
? factory.createTemplateMiddle
|
|
306
|
-
: factory.createTemplateTail)(
|
|
307
|
-
)
|
|
308
|
-
|
|
327
|
+
: factory.createTemplateTail)(literals[i + 1]!),
|
|
328
|
+
),
|
|
329
|
+
),
|
|
309
330
|
);
|
|
310
331
|
};
|
|
311
332
|
if (!ctx.route.query) return out(template());
|
|
@@ -70,7 +70,8 @@ export namespace NestiaMigrateApiSimulationProgrammer {
|
|
|
70
70
|
ctx,
|
|
71
71
|
ctx.route.parameters.length === 0 &&
|
|
72
72
|
ctx.route.query === null &&
|
|
73
|
-
ctx.route.body === null
|
|
73
|
+
ctx.route.body === null &&
|
|
74
|
+
ctx.route.headers === null
|
|
74
75
|
? "_connection"
|
|
75
76
|
: undefined,
|
|
76
77
|
),
|
|
@@ -127,6 +128,20 @@ export namespace NestiaMigrateApiSimulationProgrammer {
|
|
|
127
128
|
},
|
|
128
129
|
]
|
|
129
130
|
: []),
|
|
131
|
+
// the headers travel in the connection, validated as the server does
|
|
132
|
+
...(ctx.route.headers
|
|
133
|
+
? [
|
|
134
|
+
{
|
|
135
|
+
category: "headers",
|
|
136
|
+
name: ctx.route.headers.key,
|
|
137
|
+
schema: NestiaMigrateSchemaProgrammer.write({
|
|
138
|
+
components: ctx.components,
|
|
139
|
+
importer: ctx.importer,
|
|
140
|
+
schema: ctx.route.headers.schema,
|
|
141
|
+
}),
|
|
142
|
+
},
|
|
143
|
+
]
|
|
144
|
+
: []),
|
|
130
145
|
];
|
|
131
146
|
if (parameters.length === 0) return [];
|
|
132
147
|
|
|
@@ -206,7 +221,9 @@ export namespace NestiaMigrateApiSimulationProgrammer {
|
|
|
206
221
|
),
|
|
207
222
|
"assert",
|
|
208
223
|
),
|
|
209
|
-
|
|
224
|
+
// `connection.headers` is typed optional with optional
|
|
225
|
+
// members, so the headers name their own type to be checked
|
|
226
|
+
p.category === "headers" ? [p.schema] : undefined,
|
|
210
227
|
[
|
|
211
228
|
p.category === "headers"
|
|
212
229
|
? factory.createIdentifier("connection.headers")
|
|
@@ -218,78 +235,11 @@ export namespace NestiaMigrateApiSimulationProgrammer {
|
|
|
218
235
|
),
|
|
219
236
|
)
|
|
220
237
|
.map(factory.createExpressionStatement) as ts.Statement[];
|
|
221
|
-
|
|
238
|
+
// the SDK fetches without propagation, so an invalid simulated request
|
|
239
|
+
// throws its HttpError as the real one does, instead of answering a
|
|
240
|
+
// propagation object in place of the response
|
|
241
|
+
return [validator, ...individual];
|
|
222
242
|
};
|
|
223
|
-
|
|
224
|
-
const tryAndCatch = (
|
|
225
|
-
importer: NestiaMigrateImportProgrammer,
|
|
226
|
-
individual: ts.Statement[],
|
|
227
|
-
) =>
|
|
228
|
-
factory.createTryStatement(
|
|
229
|
-
factory.createBlock(individual, true),
|
|
230
|
-
factory.createCatchClause(
|
|
231
|
-
"exp",
|
|
232
|
-
factory.createBlock(
|
|
233
|
-
[
|
|
234
|
-
factory.createIfStatement(
|
|
235
|
-
factory.createLogicalNot(
|
|
236
|
-
factory.createCallExpression(
|
|
237
|
-
IdentifierFactory.access(
|
|
238
|
-
factory.createIdentifier(
|
|
239
|
-
importer.external({
|
|
240
|
-
type: "default",
|
|
241
|
-
library: "typia",
|
|
242
|
-
name: "typia",
|
|
243
|
-
}),
|
|
244
|
-
),
|
|
245
|
-
"is",
|
|
246
|
-
),
|
|
247
|
-
[
|
|
248
|
-
factory.createTypeReferenceNode(
|
|
249
|
-
importer.external({
|
|
250
|
-
type: "instance",
|
|
251
|
-
library: "@nestia/fetcher",
|
|
252
|
-
name: "HttpError",
|
|
253
|
-
}),
|
|
254
|
-
),
|
|
255
|
-
],
|
|
256
|
-
[factory.createIdentifier("exp")],
|
|
257
|
-
),
|
|
258
|
-
),
|
|
259
|
-
factory.createThrowStatement(factory.createIdentifier("exp")),
|
|
260
|
-
),
|
|
261
|
-
factory.createReturnStatement(
|
|
262
|
-
factory.createAsExpression(
|
|
263
|
-
factory.createObjectLiteralExpression(
|
|
264
|
-
[
|
|
265
|
-
factory.createPropertyAssignment(
|
|
266
|
-
"success",
|
|
267
|
-
factory.createFalse(),
|
|
268
|
-
),
|
|
269
|
-
factory.createPropertyAssignment(
|
|
270
|
-
"status",
|
|
271
|
-
factory.createIdentifier("exp.status"),
|
|
272
|
-
),
|
|
273
|
-
factory.createPropertyAssignment(
|
|
274
|
-
"headers",
|
|
275
|
-
factory.createIdentifier("exp.headers"),
|
|
276
|
-
),
|
|
277
|
-
factory.createPropertyAssignment(
|
|
278
|
-
"data",
|
|
279
|
-
factory.createIdentifier("exp.toJSON().message"),
|
|
280
|
-
),
|
|
281
|
-
],
|
|
282
|
-
true,
|
|
283
|
-
),
|
|
284
|
-
TypeFactory.keyword("any"),
|
|
285
|
-
),
|
|
286
|
-
),
|
|
287
|
-
],
|
|
288
|
-
true,
|
|
289
|
-
),
|
|
290
|
-
),
|
|
291
|
-
undefined,
|
|
292
|
-
);
|
|
293
243
|
}
|
|
294
244
|
|
|
295
245
|
const constant = (name: string, expression: ts.Expression) =>
|
|
@@ -91,14 +91,6 @@ export namespace NestiaMigrateE2eFunctionProgrammer {
|
|
|
91
91
|
factory.createIdentifier("api.functional"),
|
|
92
92
|
factory.createIdentifier(ctx.route.accessor.join(".")),
|
|
93
93
|
);
|
|
94
|
-
const connection = factory.createIdentifier("connection");
|
|
95
|
-
if (
|
|
96
|
-
ctx.route.parameters.length === 0 &&
|
|
97
|
-
ctx.route.query === null &&
|
|
98
|
-
ctx.route.body === null
|
|
99
|
-
)
|
|
100
|
-
return factory.createCallExpression(fetch, undefined, [connection]);
|
|
101
|
-
|
|
102
94
|
const random = factory.createPropertyAccessExpression(
|
|
103
95
|
factory.createIdentifier(
|
|
104
96
|
ctx.importer.external({
|
|
@@ -109,6 +101,47 @@ export namespace NestiaMigrateE2eFunctionProgrammer {
|
|
|
109
101
|
),
|
|
110
102
|
"random",
|
|
111
103
|
);
|
|
104
|
+
// the route's headers travel in the connection, as the server requires
|
|
105
|
+
const connection: ts.Expression = ctx.route.headers
|
|
106
|
+
? factory.createObjectLiteralExpression(
|
|
107
|
+
[
|
|
108
|
+
factory.createSpreadAssignment(
|
|
109
|
+
factory.createIdentifier("connection"),
|
|
110
|
+
),
|
|
111
|
+
factory.createPropertyAssignment(
|
|
112
|
+
"headers",
|
|
113
|
+
factory.createObjectLiteralExpression(
|
|
114
|
+
[
|
|
115
|
+
factory.createSpreadAssignment(
|
|
116
|
+
factory.createIdentifier("connection.headers"),
|
|
117
|
+
),
|
|
118
|
+
factory.createSpreadAssignment(
|
|
119
|
+
factory.createCallExpression(
|
|
120
|
+
random,
|
|
121
|
+
[
|
|
122
|
+
NestiaMigrateSchemaProgrammer.write({
|
|
123
|
+
components: ctx.components,
|
|
124
|
+
importer: ctx.importer,
|
|
125
|
+
schema: ctx.route.headers.schema,
|
|
126
|
+
}),
|
|
127
|
+
],
|
|
128
|
+
undefined,
|
|
129
|
+
),
|
|
130
|
+
),
|
|
131
|
+
],
|
|
132
|
+
true,
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
],
|
|
136
|
+
true,
|
|
137
|
+
)
|
|
138
|
+
: factory.createIdentifier("connection");
|
|
139
|
+
if (
|
|
140
|
+
ctx.route.parameters.length === 0 &&
|
|
141
|
+
ctx.route.query === null &&
|
|
142
|
+
ctx.route.body === null
|
|
143
|
+
)
|
|
144
|
+
return factory.createCallExpression(fetch, undefined, [connection]);
|
|
112
145
|
if (ctx.config.keyword === true)
|
|
113
146
|
return factory.createCallExpression(fetch, undefined, [
|
|
114
147
|
connection,
|
|
@@ -2,6 +2,7 @@ import { SyntaxKind, factory } from "@ttsc/factory";
|
|
|
2
2
|
import { IHttpMigrateRoute } from "@typia/interface";
|
|
3
3
|
import { OpenApi } from "typia";
|
|
4
4
|
|
|
5
|
+
import { NestiaMigrateControllerAnalyzer } from "../analyzers/NestiaMigrateControllerAnalyzer";
|
|
5
6
|
import { ExpressionFactory } from "../factories/ExpressionFactory";
|
|
6
7
|
import { IdentifierFactory } from "../factories/IdentifierFactory";
|
|
7
8
|
import { LiteralFactory } from "../factories/LiteralFactory";
|
|
@@ -10,7 +11,9 @@ import ts from "../internal/ts";
|
|
|
10
11
|
import { INestiaMigrateConfig } from "../structures/INestiaMigrateConfig";
|
|
11
12
|
import { INestiaMigrateController } from "../structures/INestiaMigrateController";
|
|
12
13
|
import { FilePrinter } from "../utils/FilePrinter";
|
|
14
|
+
import { RequestBody } from "../utils/RequestBody";
|
|
13
15
|
import { StringUtil } from "../utils/StringUtil";
|
|
16
|
+
import { SuccessStatus } from "../utils/SuccessStatus";
|
|
14
17
|
import { NestiaMigrateImportProgrammer } from "./NestiaMigrateImportProgrammer";
|
|
15
18
|
import { NestiaMigrateSchemaProgrammer } from "./NestiaMigrateSchemaProgrammer";
|
|
16
19
|
|
|
@@ -121,7 +124,9 @@ export namespace NestiaMigrateNestMethodProgrammer {
|
|
|
121
124
|
);
|
|
122
125
|
|
|
123
126
|
// ROUTER
|
|
124
|
-
const localPath: string =
|
|
127
|
+
const localPath: string = NestiaMigrateControllerAnalyzer.routePath(
|
|
128
|
+
ctx.route,
|
|
129
|
+
)
|
|
125
130
|
.slice(ctx.controller.path.length)
|
|
126
131
|
.split("/")
|
|
127
132
|
.filter((str) => !!str.length)
|
|
@@ -139,35 +144,43 @@ export namespace NestiaMigrateNestMethodProgrammer {
|
|
|
139
144
|
: undefined,
|
|
140
145
|
),
|
|
141
146
|
);
|
|
147
|
+
// a vanilla NestJS method decorator, relative to the controller like the
|
|
148
|
+
// nestia ones: the document's full path would be joined onto the
|
|
149
|
+
// controller's again
|
|
150
|
+
const nest = (method: string) =>
|
|
151
|
+
factory.createDecorator(
|
|
152
|
+
factory.createCallExpression(
|
|
153
|
+
external("@nestjs/common", method),
|
|
154
|
+
[],
|
|
155
|
+
localPath.length
|
|
156
|
+
? [factory.createStringLiteral(localPath)]
|
|
157
|
+
: undefined,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
142
160
|
if (ctx.route.success?.["x-nestia-encrypted"])
|
|
143
161
|
decorators.push(router("EncryptedRoute"));
|
|
144
162
|
else if (ctx.route.success?.type === "text/plain")
|
|
145
|
-
decorators.push(
|
|
146
|
-
factory.createDecorator(
|
|
147
|
-
factory.createCallExpression(
|
|
148
|
-
external("@nestjs/common", StringUtil.capitalize(ctx.route.method)),
|
|
149
|
-
[],
|
|
150
|
-
[factory.createStringLiteral(ctx.route.path)],
|
|
151
|
-
),
|
|
152
|
-
),
|
|
153
|
-
);
|
|
163
|
+
decorators.push(nest(StringUtil.capitalize(ctx.route.method)));
|
|
154
164
|
else if (ctx.route.success?.type === "application/x-www-form-urlencoded")
|
|
155
165
|
decorators.push(router("TypedQuery"));
|
|
156
|
-
else if (ctx.route.method === "head")
|
|
166
|
+
else if (ctx.route.method === "head") decorators.push(nest("Head"));
|
|
167
|
+
else if (
|
|
168
|
+
ctx.route.success === null ||
|
|
169
|
+
ctx.route.success?.type === "application/json"
|
|
170
|
+
)
|
|
171
|
+
decorators.push(router("TypedRoute"));
|
|
172
|
+
// NestJS answers 201 for POST and 200 otherwise
|
|
173
|
+
const status: number | null = SuccessStatus.of(ctx.route);
|
|
174
|
+
if (status !== null && status !== SuccessStatus.nest(ctx.route))
|
|
157
175
|
decorators.push(
|
|
158
176
|
factory.createDecorator(
|
|
159
177
|
factory.createCallExpression(
|
|
160
|
-
external("@nestjs/common", "
|
|
161
|
-
|
|
162
|
-
[
|
|
178
|
+
external("@nestjs/common", "HttpCode"),
|
|
179
|
+
undefined,
|
|
180
|
+
[ExpressionFactory.number(status)],
|
|
163
181
|
),
|
|
164
182
|
),
|
|
165
183
|
);
|
|
166
|
-
else if (
|
|
167
|
-
ctx.route.success === null ||
|
|
168
|
-
ctx.route.success?.type === "application/json"
|
|
169
|
-
)
|
|
170
|
-
decorators.push(router("TypedRoute"));
|
|
171
184
|
for (const [key, value] of Object.entries(ctx.route.exceptions ?? {}))
|
|
172
185
|
decorators.push(
|
|
173
186
|
factory.createDecorator(
|
|
@@ -291,11 +304,7 @@ export namespace NestiaMigrateNestMethodProgrammer {
|
|
|
291
304
|
: [],
|
|
292
305
|
})(ctx.components)(ctx.importer)({
|
|
293
306
|
schema: ctx.route.body.schema,
|
|
294
|
-
required:
|
|
295
|
-
(ctx.route.body.type === "application/json" ||
|
|
296
|
-
ctx.route.body.type === "text/plain") &&
|
|
297
|
-
ctx.route.operation().requestBody?.required === false
|
|
298
|
-
),
|
|
307
|
+
required: RequestBody.optional(ctx.route) === false,
|
|
299
308
|
example: ctx.route.body.media().example,
|
|
300
309
|
examples: ctx.route.body.media().examples,
|
|
301
310
|
}),
|
|
@@ -380,8 +389,10 @@ export namespace NestiaMigrateNestMethodProgrammer {
|
|
|
380
389
|
),
|
|
381
390
|
]
|
|
382
391
|
: []),
|
|
383
|
-
...Object.entries(media.examples ?? {}).
|
|
384
|
-
|
|
392
|
+
...Object.entries(media.examples ?? {}).flatMap(([key, example]) => {
|
|
393
|
+
const value: { value: unknown } | null = exampleValue(example);
|
|
394
|
+
if (value === null) return [];
|
|
395
|
+
return factory.createDecorator(
|
|
385
396
|
factory.createCallExpression(
|
|
386
397
|
IdentifierFactory.access(
|
|
387
398
|
factory.createIdentifier(
|
|
@@ -394,9 +405,57 @@ export namespace NestiaMigrateNestMethodProgrammer {
|
|
|
394
405
|
kind,
|
|
395
406
|
),
|
|
396
407
|
[],
|
|
397
|
-
[
|
|
408
|
+
[
|
|
409
|
+
factory.createStringLiteral(key),
|
|
410
|
+
LiteralFactory.write(value.value),
|
|
411
|
+
],
|
|
398
412
|
),
|
|
399
|
-
)
|
|
400
|
-
),
|
|
413
|
+
);
|
|
414
|
+
}),
|
|
401
415
|
];
|
|
402
416
|
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The value a named example stands for, or `null` when it names none.
|
|
420
|
+
*
|
|
421
|
+
* OpenAPI names examples with Example Objects, whose `value` (or, since 3.2,
|
|
422
|
+
* `dataValue`) holds the value, and a `$ref` to one arrives resolved. Documents
|
|
423
|
+
* from earlier nestia versions held the values themselves, so an object is read
|
|
424
|
+
* as an Example Object only when it is one: it has one of the value fields, and
|
|
425
|
+
* every other key is an Example Object field or an extension. A raw value of
|
|
426
|
+
* exactly that shape is ambiguous and is read as an Example Object, as a
|
|
427
|
+
* conforming document means it. An Example Object that only points at its value
|
|
428
|
+
* (`externalValue`) or only serializes it (`serializedValue`) gives
|
|
429
|
+
* `SwaggerExample` nothing to hold.
|
|
430
|
+
*/
|
|
431
|
+
const exampleValue = (example: unknown): { value: unknown } | null => {
|
|
432
|
+
if (isExampleObject(example) === false) return { value: example };
|
|
433
|
+
const record = example as Record<string, unknown>;
|
|
434
|
+
if (Object.prototype.hasOwnProperty.call(record, "value"))
|
|
435
|
+
return { value: record.value };
|
|
436
|
+
if (Object.prototype.hasOwnProperty.call(record, "dataValue"))
|
|
437
|
+
return { value: record.dataValue };
|
|
438
|
+
return null;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const isExampleObject = (example: unknown): boolean =>
|
|
442
|
+
typeof example === "object" &&
|
|
443
|
+
example !== null &&
|
|
444
|
+
Array.isArray(example) === false &&
|
|
445
|
+
VALUE_KEYS.some((key) =>
|
|
446
|
+
Object.prototype.hasOwnProperty.call(example, key),
|
|
447
|
+
) &&
|
|
448
|
+
Object.keys(example).every(
|
|
449
|
+
(key) =>
|
|
450
|
+
VALUE_KEYS.includes(key) ||
|
|
451
|
+
key === "summary" ||
|
|
452
|
+
key === "description" ||
|
|
453
|
+
key.startsWith("x-"),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const VALUE_KEYS: string[] = [
|
|
457
|
+
"value",
|
|
458
|
+
"dataValue",
|
|
459
|
+
"serializedValue",
|
|
460
|
+
"externalValue",
|
|
461
|
+
];
|
|
@@ -123,12 +123,29 @@ export namespace NestiaMigrateSchemaProgrammer {
|
|
|
123
123
|
writeNumeric({
|
|
124
124
|
factory: () => [
|
|
125
125
|
TypeFactory.keyword("number"),
|
|
126
|
-
props.importer.tag("Type",
|
|
126
|
+
props.importer.tag("Type", integerType(props.schema)),
|
|
127
127
|
],
|
|
128
128
|
importer: props.importer,
|
|
129
129
|
schema: props.schema,
|
|
130
130
|
});
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* The typia integer type admitting what the schema admits: its own `format`
|
|
134
|
+
* when it names one typia has, else `int64`, as an integer schema without a
|
|
135
|
+
* format admits any integer.
|
|
136
|
+
*/
|
|
137
|
+
const integerType = (
|
|
138
|
+
schema: OpenApi.IJsonSchema.IInteger,
|
|
139
|
+
): "int32" | "uint32" | "int64" | "uint64" => {
|
|
140
|
+
const format: unknown = (schema as { format?: unknown }).format;
|
|
141
|
+
return format === "int32" ||
|
|
142
|
+
format === "uint32" ||
|
|
143
|
+
format === "int64" ||
|
|
144
|
+
format === "uint64"
|
|
145
|
+
? format
|
|
146
|
+
: "int64";
|
|
147
|
+
};
|
|
148
|
+
|
|
132
149
|
const writeNumber = (props: {
|
|
133
150
|
importer: NestiaMigrateImportProgrammer;
|
|
134
151
|
schema: OpenApi.IJsonSchema.INumber;
|
|
@@ -147,19 +164,19 @@ export namespace NestiaMigrateSchemaProgrammer {
|
|
|
147
164
|
const intersection: ts.TypeNode[] = props.factory();
|
|
148
165
|
if (props.schema.default !== undefined)
|
|
149
166
|
intersection.push(props.importer.tag("Default", props.schema.default));
|
|
167
|
+
// the emended schema holds each bound as a number, as OpenAPI 3.1 does;
|
|
168
|
+
// an OpenAPI 3.0 boolean `exclusiveMinimum` is converted into that form
|
|
150
169
|
if (props.schema.minimum !== undefined)
|
|
170
|
+
intersection.push(props.importer.tag("Minimum", props.schema.minimum));
|
|
171
|
+
if (typeof props.schema.exclusiveMinimum === "number")
|
|
151
172
|
intersection.push(
|
|
152
|
-
props.importer.tag(
|
|
153
|
-
props.schema.exclusiveMinimum ? "ExclusiveMinimum" : "Minimum",
|
|
154
|
-
props.schema.minimum,
|
|
155
|
-
),
|
|
173
|
+
props.importer.tag("ExclusiveMinimum", props.schema.exclusiveMinimum),
|
|
156
174
|
);
|
|
157
175
|
if (props.schema.maximum !== undefined)
|
|
176
|
+
intersection.push(props.importer.tag("Maximum", props.schema.maximum));
|
|
177
|
+
if (typeof props.schema.exclusiveMaximum === "number")
|
|
158
178
|
intersection.push(
|
|
159
|
-
props.importer.tag(
|
|
160
|
-
props.schema.exclusiveMaximum ? "ExclusiveMaximum" : "Maximum",
|
|
161
|
-
props.schema.maximum,
|
|
162
|
-
),
|
|
179
|
+
props.importer.tag("ExclusiveMaximum", props.schema.exclusiveMaximum),
|
|
163
180
|
);
|
|
164
181
|
if (props.schema.multipleOf !== undefined)
|
|
165
182
|
intersection.push(
|
|
@@ -299,7 +316,8 @@ export namespace NestiaMigrateSchemaProgrammer {
|
|
|
299
316
|
schema: props.schema.additionalProperties as OpenApi.IJsonSchema,
|
|
300
317
|
}),
|
|
301
318
|
]);
|
|
302
|
-
|
|
319
|
+
// `properties` is a record, which has no length
|
|
320
|
+
return Object.keys(props.schema.properties ?? {}).length !== 0 &&
|
|
303
321
|
typeof props.schema.additionalProperties === "object"
|
|
304
322
|
? factory.createIntersectionTypeNode([regular(), dynamic()])
|
|
305
323
|
: typeof props.schema.additionalProperties === "object"
|