@autoview/cli 0.1.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 (297) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +407 -0
  3. package/lib/AutoViewAgent.d.ts +109 -0
  4. package/lib/AutoViewAgent.js +123 -0
  5. package/lib/AutoViewAgent.js.map +1 -0
  6. package/lib/agent/emitMcpServer.d.ts +15 -0
  7. package/lib/agent/emitMcpServer.js +157 -0
  8. package/lib/agent/emitMcpServer.js.map +1 -0
  9. package/lib/agent/emitReport.d.ts +14 -0
  10. package/lib/agent/emitReport.js +85 -0
  11. package/lib/agent/emitReport.js.map +1 -0
  12. package/lib/agent/toolSurface.d.ts +130 -0
  13. package/lib/agent/toolSurface.js +342 -0
  14. package/lib/agent/toolSurface.js.map +1 -0
  15. package/lib/agent/verifyAgentTasks.d.ts +87 -0
  16. package/lib/agent/verifyAgentTasks.js +126 -0
  17. package/lib/agent/verifyAgentTasks.js.map +1 -0
  18. package/lib/cli/main.d.ts +2 -0
  19. package/lib/cli/main.js +295 -0
  20. package/lib/cli/main.js.map +1 -0
  21. package/lib/compiler/AutoViewInterfaceCompiler.d.ts +27 -0
  22. package/lib/compiler/AutoViewInterfaceCompiler.js +68 -0
  23. package/lib/compiler/AutoViewInterfaceCompiler.js.map +1 -0
  24. package/lib/constants/AutoViewFrontendTemplate.d.ts +1 -0
  25. package/lib/constants/AutoViewFrontendTemplate.js +46 -0
  26. package/lib/constants/AutoViewFrontendTemplate.js.map +1 -0
  27. package/lib/constants/AutoViewSystemPromptConstant.d.ts +5 -0
  28. package/lib/constants/AutoViewSystemPromptConstant.js +4 -0
  29. package/lib/constants/AutoViewSystemPromptConstant.js.map +1 -0
  30. package/lib/context/IAutoViewAgentContext.d.ts +60 -0
  31. package/lib/context/IAutoViewAgentContext.js +3 -0
  32. package/lib/context/IAutoViewAgentContext.js.map +1 -0
  33. package/lib/fromSwagger.d.ts +53 -0
  34. package/lib/fromSwagger.js +513 -0
  35. package/lib/fromSwagger.js.map +1 -0
  36. package/lib/generateDeterministic.d.ts +26 -0
  37. package/lib/generateDeterministic.js +75 -0
  38. package/lib/generateDeterministic.js.map +1 -0
  39. package/lib/index.d.ts +15 -0
  40. package/lib/index.js +41 -0
  41. package/lib/index.js.map +1 -0
  42. package/lib/orchestrate/orchestrateAutoView.d.ts +17 -0
  43. package/lib/orchestrate/orchestrateAutoView.js +491 -0
  44. package/lib/orchestrate/orchestrateAutoView.js.map +1 -0
  45. package/lib/orchestrate/orchestrateAutoViewProductPlan.d.ts +37 -0
  46. package/lib/orchestrate/orchestrateAutoViewProductPlan.js +109 -0
  47. package/lib/orchestrate/orchestrateAutoViewProductPlan.js.map +1 -0
  48. package/lib/orchestrate/orchestrateAutoViewRender.d.ts +133 -0
  49. package/lib/orchestrate/orchestrateAutoViewRender.js +943 -0
  50. package/lib/orchestrate/orchestrateAutoViewRender.js.map +1 -0
  51. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.d.ts +24 -0
  52. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js +92 -0
  53. package/lib/orchestrate/orchestrateAutoViewRenderDeterministic.js.map +1 -0
  54. package/lib/orchestrate/orchestrateAutoViewReview.d.ts +48 -0
  55. package/lib/orchestrate/orchestrateAutoViewReview.js +328 -0
  56. package/lib/orchestrate/orchestrateAutoViewReview.js.map +1 -0
  57. package/lib/orchestrate/orchestrateAutoViewScaffold.d.ts +45 -0
  58. package/lib/orchestrate/orchestrateAutoViewScaffold.js +586 -0
  59. package/lib/orchestrate/orchestrateAutoViewScaffold.js.map +1 -0
  60. package/lib/orchestrate/orchestrateAutoViewSdkStudy.d.ts +26 -0
  61. package/lib/orchestrate/orchestrateAutoViewSdkStudy.js +85 -0
  62. package/lib/orchestrate/orchestrateAutoViewSdkStudy.js.map +1 -0
  63. package/lib/orchestrate/structures/IAutoViewProductPlan.d.ts +96 -0
  64. package/lib/orchestrate/structures/IAutoViewProductPlan.js +3 -0
  65. package/lib/orchestrate/structures/IAutoViewProductPlan.js.map +1 -0
  66. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.d.ts +38 -0
  67. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js +3 -0
  68. package/lib/orchestrate/structures/IAutoViewProductPlanApplication.js.map +1 -0
  69. package/lib/orchestrate/structures/IAutoViewRenderApplication.d.ts +38 -0
  70. package/lib/orchestrate/structures/IAutoViewRenderApplication.js +3 -0
  71. package/lib/orchestrate/structures/IAutoViewRenderApplication.js.map +1 -0
  72. package/lib/orchestrate/structures/IAutoViewReviewApplication.d.ts +40 -0
  73. package/lib/orchestrate/structures/IAutoViewReviewApplication.js +3 -0
  74. package/lib/orchestrate/structures/IAutoViewReviewApplication.js.map +1 -0
  75. package/lib/orchestrate/structures/IAutoViewSdkMap.d.ts +63 -0
  76. package/lib/orchestrate/structures/IAutoViewSdkMap.js +3 -0
  77. package/lib/orchestrate/structures/IAutoViewSdkMap.js.map +1 -0
  78. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.d.ts +37 -0
  79. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js +3 -0
  80. package/lib/orchestrate/structures/IAutoViewSdkStudyApplication.js.map +1 -0
  81. package/lib/orchestrate/utils/HistoryMessage.d.ts +10 -0
  82. package/lib/orchestrate/utils/HistoryMessage.js +25 -0
  83. package/lib/orchestrate/utils/HistoryMessage.js.map +1 -0
  84. package/lib/orchestrate/utils/auditFrontendRuntime.d.ts +53 -0
  85. package/lib/orchestrate/utils/auditFrontendRuntime.js +362 -0
  86. package/lib/orchestrate/utils/auditFrontendRuntime.js.map +1 -0
  87. package/lib/orchestrate/utils/buildDeterministicPlan.d.ts +4 -0
  88. package/lib/orchestrate/utils/buildDeterministicPlan.js +233 -0
  89. package/lib/orchestrate/utils/buildDeterministicPlan.js.map +1 -0
  90. package/lib/orchestrate/utils/buildDeterministicSdkMap.d.ts +22 -0
  91. package/lib/orchestrate/utils/buildDeterministicSdkMap.js +154 -0
  92. package/lib/orchestrate/utils/buildDeterministicSdkMap.js.map +1 -0
  93. package/lib/orchestrate/utils/cacheNodeModules.d.ts +31 -0
  94. package/lib/orchestrate/utils/cacheNodeModules.js +134 -0
  95. package/lib/orchestrate/utils/cacheNodeModules.js.map +1 -0
  96. package/lib/orchestrate/utils/describeEndpointPropsShape.d.ts +37 -0
  97. package/lib/orchestrate/utils/describeEndpointPropsShape.js +192 -0
  98. package/lib/orchestrate/utils/describeEndpointPropsShape.js.map +1 -0
  99. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.d.ts +22 -0
  100. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js +29 -0
  101. package/lib/orchestrate/utils/describeEndpointRequestBodyShape.js.map +1 -0
  102. package/lib/orchestrate/utils/describeEndpointResponseShape.d.ts +19 -0
  103. package/lib/orchestrate/utils/describeEndpointResponseShape.js +30 -0
  104. package/lib/orchestrate/utils/describeEndpointResponseShape.js.map +1 -0
  105. package/lib/orchestrate/utils/executeCachedBatch.d.ts +22 -0
  106. package/lib/orchestrate/utils/executeCachedBatch.js +64 -0
  107. package/lib/orchestrate/utils/executeCachedBatch.js.map +1 -0
  108. package/lib/orchestrate/utils/loadShoppingFixture.d.ts +33 -0
  109. package/lib/orchestrate/utils/loadShoppingFixture.js +17 -0
  110. package/lib/orchestrate/utils/loadShoppingFixture.js.map +1 -0
  111. package/lib/orchestrate/utils/normalizeProductPlanPaths.d.ts +24 -0
  112. package/lib/orchestrate/utils/normalizeProductPlanPaths.js +77 -0
  113. package/lib/orchestrate/utils/normalizeProductPlanPaths.js.map +1 -0
  114. package/lib/orchestrate/utils/renderJsonSchema.d.ts +23 -0
  115. package/lib/orchestrate/utils/renderJsonSchema.js +122 -0
  116. package/lib/orchestrate/utils/renderJsonSchema.js.map +1 -0
  117. package/lib/orchestrate/utils/renderResourcePage.d.ts +36 -0
  118. package/lib/orchestrate/utils/renderResourcePage.js +1415 -0
  119. package/lib/orchestrate/utils/renderResourcePage.js.map +1 -0
  120. package/lib/orchestrate/utils/validateFrontendTypecheck.d.ts +109 -0
  121. package/lib/orchestrate/utils/validateFrontendTypecheck.js +274 -0
  122. package/lib/orchestrate/utils/validateFrontendTypecheck.js.map +1 -0
  123. package/lib/preview/renderPreview.d.ts +22 -0
  124. package/lib/preview/renderPreview.js +198 -0
  125. package/lib/preview/renderPreview.js.map +1 -0
  126. package/lib/typings/compiler.d.ts +39 -0
  127. package/lib/typings/compiler.js +3 -0
  128. package/lib/typings/compiler.js.map +1 -0
  129. package/lib/typings/events.d.ts +106 -0
  130. package/lib/typings/events.js +3 -0
  131. package/lib/typings/events.js.map +1 -0
  132. package/lib/typings/index.d.ts +10 -0
  133. package/lib/typings/index.js +27 -0
  134. package/lib/typings/index.js.map +1 -0
  135. package/lib/typings/misc.d.ts +78 -0
  136. package/lib/typings/misc.js +3 -0
  137. package/lib/typings/misc.js.map +1 -0
  138. package/lib/utils/ArrayUtil.d.ts +8 -0
  139. package/lib/utils/ArrayUtil.js +30 -0
  140. package/lib/utils/ArrayUtil.js.map +1 -0
  141. package/lib/utils/StringUtil.d.ts +11 -0
  142. package/lib/utils/StringUtil.js +28 -0
  143. package/lib/utils/StringUtil.js.map +1 -0
  144. package/lib/utils/classifyEndpoints.d.ts +62 -0
  145. package/lib/utils/classifyEndpoints.js +216 -0
  146. package/lib/utils/classifyEndpoints.js.map +1 -0
  147. package/lib/utils/endpointFilter.d.ts +26 -0
  148. package/lib/utils/endpointFilter.js +0 -0
  149. package/lib/utils/endpointFilter.js.map +1 -0
  150. package/lib/utils/extractFields.d.ts +85 -0
  151. package/lib/utils/extractFields.js +231 -0
  152. package/lib/utils/extractFields.js.map +1 -0
  153. package/lib/utils/index.d.ts +13 -0
  154. package/lib/utils/index.js +30 -0
  155. package/lib/utils/index.js.map +1 -0
  156. package/lib/utils/normalizeForNestia.d.ts +34 -0
  157. package/lib/utils/normalizeForNestia.js +133 -0
  158. package/lib/utils/normalizeForNestia.js.map +1 -0
  159. package/lib/utils/resourcePlan.d.ts +39 -0
  160. package/lib/utils/resourcePlan.js +95 -0
  161. package/lib/utils/resourcePlan.js.map +1 -0
  162. package/lib/utils/sliceDocument.d.ts +17 -0
  163. package/lib/utils/sliceDocument.js +114 -0
  164. package/lib/utils/sliceDocument.js.map +1 -0
  165. package/lib/utils/toEndpoints.d.ts +90 -0
  166. package/lib/utils/toEndpoints.js +227 -0
  167. package/lib/utils/toEndpoints.js.map +1 -0
  168. package/lib/verify/runWorkflows.d.ts +25 -0
  169. package/lib/verify/runWorkflows.js +366 -0
  170. package/lib/verify/runWorkflows.js.map +1 -0
  171. package/lib/verify/workflows.d.ts +53 -0
  172. package/lib/verify/workflows.js +107 -0
  173. package/lib/verify/workflows.js.map +1 -0
  174. package/package.json +82 -0
  175. package/prompts/AUTOVIEW_RENDER.md +398 -0
  176. package/prompts/AUTOVIEW_REVIEW.md +60 -0
  177. package/prompts/AUTOVIEW_SDK_STUDY.md +89 -0
  178. package/src/AutoViewAgent.ts +222 -0
  179. package/src/agent/emitMcpServer.integration.test.ts +168 -0
  180. package/src/agent/emitMcpServer.test.ts +51 -0
  181. package/src/agent/emitMcpServer.ts +178 -0
  182. package/src/agent/emitReport.ts +117 -0
  183. package/src/agent/toolSurface.test.ts +243 -0
  184. package/src/agent/toolSurface.ts +501 -0
  185. package/src/agent/verifyAgentTasks.test.ts +106 -0
  186. package/src/agent/verifyAgentTasks.ts +171 -0
  187. package/src/cli/main.ts +363 -0
  188. package/src/compiler/AutoViewInterfaceCompiler.ts +69 -0
  189. package/src/constants/AutoViewFrontendTemplate.ts +42 -0
  190. package/src/constants/AutoViewSystemPromptConstant.ts +6 -0
  191. package/src/context/IAutoViewAgentContext.ts +84 -0
  192. package/src/fromSwagger.test.ts +269 -0
  193. package/src/fromSwagger.ts +500 -0
  194. package/src/generateDeterministic.test.ts +39 -0
  195. package/src/generateDeterministic.ts +77 -0
  196. package/src/index.ts +30 -0
  197. package/src/orchestrate/orchestrateAutoView.ts +590 -0
  198. package/src/orchestrate/orchestrateAutoViewProductPlan.ts +121 -0
  199. package/src/orchestrate/orchestrateAutoViewRender.ts +1117 -0
  200. package/src/orchestrate/orchestrateAutoViewRenderDeterministic.ts +101 -0
  201. package/src/orchestrate/orchestrateAutoViewReview.ts +272 -0
  202. package/src/orchestrate/orchestrateAutoViewScaffold.ts +627 -0
  203. package/src/orchestrate/orchestrateAutoViewSdkStudy.ts +90 -0
  204. package/src/orchestrate/renderNavTs.test.ts +74 -0
  205. package/src/orchestrate/structures/IAutoViewProductPlan.ts +119 -0
  206. package/src/orchestrate/structures/IAutoViewProductPlanApplication.ts +41 -0
  207. package/src/orchestrate/structures/IAutoViewRenderApplication.ts +40 -0
  208. package/src/orchestrate/structures/IAutoViewReviewApplication.ts +42 -0
  209. package/src/orchestrate/structures/IAutoViewSdkMap.ts +72 -0
  210. package/src/orchestrate/structures/IAutoViewSdkStudyApplication.ts +40 -0
  211. package/src/orchestrate/utils/HistoryMessage.ts +41 -0
  212. package/src/orchestrate/utils/auditFrontendRuntime.test.ts +18 -0
  213. package/src/orchestrate/utils/auditFrontendRuntime.ts +454 -0
  214. package/src/orchestrate/utils/buildDeterministicPlan.test.ts +170 -0
  215. package/src/orchestrate/utils/buildDeterministicPlan.ts +289 -0
  216. package/src/orchestrate/utils/buildDeterministicSdkMap.test.ts +90 -0
  217. package/src/orchestrate/utils/buildDeterministicSdkMap.ts +169 -0
  218. package/src/orchestrate/utils/cacheNodeModules.ts +136 -0
  219. package/src/orchestrate/utils/describeEndpointPropsShape.test.ts +86 -0
  220. package/src/orchestrate/utils/describeEndpointPropsShape.ts +202 -0
  221. package/src/orchestrate/utils/describeEndpointRequestBodyShape.test.ts +87 -0
  222. package/src/orchestrate/utils/describeEndpointRequestBodyShape.ts +31 -0
  223. package/src/orchestrate/utils/describeEndpointResponseShape.test.ts +70 -0
  224. package/src/orchestrate/utils/describeEndpointResponseShape.ts +32 -0
  225. package/src/orchestrate/utils/executeCachedBatch.ts +59 -0
  226. package/src/orchestrate/utils/loadShoppingFixture.ts +52 -0
  227. package/src/orchestrate/utils/normalizeProductPlanPaths.ts +92 -0
  228. package/src/orchestrate/utils/renderJsonSchema.test.ts +162 -0
  229. package/src/orchestrate/utils/renderJsonSchema.ts +133 -0
  230. package/src/orchestrate/utils/renderResourcePage.test.ts +468 -0
  231. package/src/orchestrate/utils/renderResourcePage.ts +1624 -0
  232. package/src/orchestrate/utils/validateFrontendTypecheck.test.ts +32 -0
  233. package/src/orchestrate/utils/validateFrontendTypecheck.ts +335 -0
  234. package/src/preview/renderPreview.ts +273 -0
  235. package/src/typings/compiler.ts +47 -0
  236. package/src/typings/events.ts +155 -0
  237. package/src/typings/index.ts +10 -0
  238. package/src/typings/misc.ts +93 -0
  239. package/src/utils/ArrayUtil.ts +16 -0
  240. package/src/utils/StringUtil.ts +29 -0
  241. package/src/utils/classifyEndpoints.test.ts +86 -0
  242. package/src/utils/classifyEndpoints.ts +291 -0
  243. package/src/utils/endpointFilter.test.ts +50 -0
  244. package/src/utils/endpointFilter.ts +0 -0
  245. package/src/utils/extractFields.test.ts +82 -0
  246. package/src/utils/extractFields.ts +306 -0
  247. package/src/utils/index.ts +13 -0
  248. package/src/utils/normalizeForNestia.test.ts +93 -0
  249. package/src/utils/normalizeForNestia.ts +139 -0
  250. package/src/utils/resourcePlan.test.ts +104 -0
  251. package/src/utils/resourcePlan.ts +180 -0
  252. package/src/utils/sliceDocument.test.ts +85 -0
  253. package/src/utils/sliceDocument.ts +119 -0
  254. package/src/utils/toEndpoints.test.ts +251 -0
  255. package/src/utils/toEndpoints.ts +343 -0
  256. package/src/verify/runWorkflows.ts +403 -0
  257. package/src/verify/workflows.test.ts +117 -0
  258. package/src/verify/workflows.ts +154 -0
  259. package/template/CLAUDE.md +140 -0
  260. package/template/Dockerfile +31 -0
  261. package/template/PROMPT.md +80 -0
  262. package/template/SANDBOX.md +70 -0
  263. package/template/app/api/health/route.ts +10 -0
  264. package/template/app/globals.css +97 -0
  265. package/template/app/layout.tsx +30 -0
  266. package/template/app/page.tsx +19 -0
  267. package/template/components/AppShell.tsx +114 -0
  268. package/template/components/auto/CatalogGrid.tsx +159 -0
  269. package/template/components/auto/ConfirmButton.tsx +67 -0
  270. package/template/components/auto/EmbeddedCollection.tsx +144 -0
  271. package/template/components/auto/ResourceDashboard.tsx +104 -0
  272. package/template/components/auto/ResourceDetail.tsx +93 -0
  273. package/template/components/auto/ResourceForm.tsx +235 -0
  274. package/template/components/auto/ResourceIcon.tsx +88 -0
  275. package/template/components/auto/ResourceLanding.tsx +155 -0
  276. package/template/components/auto/ResourceTable.tsx +223 -0
  277. package/template/components/auto/formatValue.tsx +186 -0
  278. package/template/components/auto/types.ts +42 -0
  279. package/template/components/ui/badge.tsx +40 -0
  280. package/template/components/ui/button.tsx +57 -0
  281. package/template/components/ui/card.tsx +86 -0
  282. package/template/components/ui/dialog.tsx +119 -0
  283. package/template/components/ui/input.tsx +23 -0
  284. package/template/components/ui/label.tsx +24 -0
  285. package/template/components/ui/pagination.tsx +117 -0
  286. package/template/components/ui/select.tsx +92 -0
  287. package/template/components/ui/sheet.tsx +135 -0
  288. package/template/components/ui/skeleton.tsx +15 -0
  289. package/template/components/ui/table.tsx +120 -0
  290. package/template/components/ui/tabs.tsx +55 -0
  291. package/template/lib/utils.ts +35 -0
  292. package/template/next.config.mjs +52 -0
  293. package/template/package.json +46 -0
  294. package/template/postcss.config.js +6 -0
  295. package/template/scripts/start-shopping-backend.sh +56 -0
  296. package/template/tailwind.config.ts +96 -0
  297. package/template/tsconfig.json +29 -0
@@ -0,0 +1,251 @@
1
+ import { OpenApiConverter } from "@typia/utils";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { toEndpoints, unsupportedOperations } from "./toEndpoints";
5
+
6
+ // Build a normalized OpenApi.IDocument from a raw paths/schemas fragment, the
7
+ // same way fromSwagger does (upgradeDocument). Keeps the fixtures terse.
8
+ function doc(
9
+ paths: Record<string, unknown>,
10
+ schemas: Record<string, unknown> = {},
11
+ ) {
12
+ return OpenApiConverter.upgradeDocument({
13
+ openapi: "3.0.0",
14
+ info: { title: "t", version: "1.0.0" },
15
+ paths,
16
+ components: { schemas },
17
+ // biome-ignore lint: test fixture
18
+ } as never);
19
+ }
20
+
21
+ const okJson = (ref: string) => ({
22
+ 200: {
23
+ description: "ok",
24
+ content: { "application/json": { schema: { $ref: ref } } },
25
+ },
26
+ });
27
+
28
+ describe("toEndpoints — swagger-native READ layer", () => {
29
+ it("preserves query-string endpoints (the bug swagger-native fixed)", () => {
30
+ const eps = toEndpoints(
31
+ doc(
32
+ {
33
+ "/items/search": {
34
+ get: {
35
+ operationId: "search",
36
+ parameters: [
37
+ { name: "q", in: "query", required: true, schema: { type: "string" } },
38
+ { name: "page", in: "query", schema: { type: "integer" } },
39
+ ],
40
+ responses: okJson("#/components/schemas/Item"),
41
+ },
42
+ },
43
+ },
44
+ { Item: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
45
+ ),
46
+ );
47
+ const search = eps.find((e) => e.path === "/items/search");
48
+ expect(search).toBeDefined();
49
+ expect(search!.query).not.toBeNull();
50
+ });
51
+
52
+ it("builds the query as a resolvable inline object (not nestia's dead $ref)", () => {
53
+ // nestia points route.query.schema at a synthetic `*.GetQuery` $ref that
54
+ // exists in no components map — reconstruct it inline from the parameters so
55
+ // a list call can fill required defaults instead of sending `query: {}`.
56
+ const eps = toEndpoints(
57
+ doc(
58
+ {
59
+ "/pet/findByStatus": {
60
+ get: {
61
+ operationId: "findByStatus",
62
+ parameters: [
63
+ { name: "status", in: "query", required: true, schema: { type: "string", enum: ["available", "pending", "sold"] } },
64
+ { name: "limit", in: "query", schema: { type: "integer" } },
65
+ ],
66
+ responses: okJson("#/components/schemas/Pet"),
67
+ },
68
+ },
69
+ },
70
+ { Pet: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
71
+ ),
72
+ );
73
+ const q = eps.find((e) => e.path === "/pet/findByStatus")!.query as {
74
+ type: string;
75
+ properties: Record<string, unknown>;
76
+ required: string[];
77
+ };
78
+ expect(q.type).toBe("object");
79
+ expect(Object.keys(q.properties).sort()).toEqual(["limit", "status"]);
80
+ expect(q.required).toEqual(["status"]); // required flag preserved
81
+ });
82
+
83
+ it("promotes inline request/response bodies into named component schemas", () => {
84
+ const d = doc({
85
+ "/items": {
86
+ post: {
87
+ operationId: "create",
88
+ requestBody: {
89
+ content: {
90
+ "application/json": {
91
+ schema: {
92
+ type: "object",
93
+ properties: { name: { type: "string" } },
94
+ required: ["name"],
95
+ },
96
+ },
97
+ },
98
+ },
99
+ responses: {
100
+ 201: {
101
+ description: "created",
102
+ content: {
103
+ "application/json": {
104
+ schema: { type: "object", properties: { id: { type: "string" } } },
105
+ },
106
+ },
107
+ },
108
+ },
109
+ },
110
+ },
111
+ });
112
+ const ep = toEndpoints(d).find((e) => e.path === "/items")!;
113
+ // Inline body is not dropped — it is promoted to a named component and the
114
+ // promoted name resolves inside the document's components.
115
+ expect(ep.requestBody).not.toBeNull();
116
+ expect((d.components?.schemas ?? {})[ep.requestBody!.typeName]).toBeDefined();
117
+ expect(ep.responseBody).not.toBeNull();
118
+ expect((d.components?.schemas ?? {})[ep.responseBody!.typeName]).toBeDefined();
119
+ });
120
+
121
+ it("picks a 202 success body, never the error `default` response", () => {
122
+ // nestia only recognizes 200/201 as success; with a 202-only operation it
123
+ // falls back to `default` (the error schema). The real success is the 202.
124
+ const d = doc(
125
+ {
126
+ "/zip_downloads": {
127
+ post: {
128
+ operationId: "create",
129
+ responses: {
130
+ 202: { description: "accepted", content: { "application/json": { schema: { $ref: "#/components/schemas/ZipDownload" } } } },
131
+ 400: { description: "bad", content: { "application/json": { schema: { $ref: "#/components/schemas/ClientError" } } } },
132
+ default: { description: "err", content: { "application/json": { schema: { $ref: "#/components/schemas/ClientError" } } } },
133
+ },
134
+ },
135
+ },
136
+ },
137
+ {
138
+ ZipDownload: { type: "object", properties: { id: { type: "string" }, download_url: { type: "string" } }, required: ["id"] },
139
+ ClientError: { type: "object", properties: { code: { type: "string" }, message: { type: "string" } } },
140
+ },
141
+ );
142
+ const ep = toEndpoints(d).find((e) => e.path === "/zip_downloads")!;
143
+ expect(ep.responseBody?.typeName).toBe("ZipDownload");
144
+ });
145
+
146
+ it("treats a 204 no-content response as a null body, not the error `default`", () => {
147
+ const d = doc(
148
+ {
149
+ "/files/{id}": {
150
+ delete: {
151
+ operationId: "erase",
152
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
153
+ responses: {
154
+ 204: { description: "deleted" },
155
+ default: { description: "err", content: { "application/json": { schema: { $ref: "#/components/schemas/ClientError" } } } },
156
+ },
157
+ },
158
+ },
159
+ },
160
+ { ClientError: { type: "object", properties: { code: { type: "string" } } } },
161
+ );
162
+ const ep = toEndpoints(d).find((e) => e.path === "/files/{id}")!;
163
+ expect(ep.responseBody).toBeNull();
164
+ expect(ep.responseSchema).toBeNull();
165
+ });
166
+
167
+ it("surfaces operations dropped for unsupported content types instead of losing them", () => {
168
+ const d = doc({
169
+ "/files/{id}": {
170
+ post: {
171
+ operationId: "upload",
172
+ requestBody: {
173
+ content: {
174
+ "application/octet-stream": {
175
+ schema: { type: "string", format: "binary" },
176
+ },
177
+ },
178
+ },
179
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
180
+ responses: { 200: { description: "ok" } },
181
+ },
182
+ },
183
+ });
184
+ const eps = toEndpoints(d);
185
+ const dropped = unsupportedOperations(d);
186
+ // The octet-stream op cannot become a route, but it must NOT vanish silently.
187
+ expect(eps.find((e) => e.path === "/files/{id}")).toBeUndefined();
188
+ expect(dropped.some((u) => u.path === "/files/{id}")).toBe(true);
189
+ });
190
+
191
+ it("exposes a list endpoint with MORE THAN ONE object query param (the dropped-list bug)", () => {
192
+ // nestia's HttpMigration refuses a route whose query has >1 object-typed
193
+ // parameter ("query typed parameters must be only one object type"). Apideck's
194
+ // `GET /accounting/invoices` carries `filter`, `sort` AND `pass_through`
195
+ // objects — the very list screen that produces ids for navigation. It must be
196
+ // exposed, and its reconstructed query must keep EVERY param (objects + prims).
197
+ const d = doc(
198
+ {
199
+ "/accounting/invoices": {
200
+ get: {
201
+ operationId: "invoicesAll",
202
+ parameters: [
203
+ { name: "filter", in: "query", schema: { $ref: "#/components/schemas/InvoicesFilter" } },
204
+ { name: "sort", in: "query", schema: { $ref: "#/components/schemas/InvoicesSort" } },
205
+ { name: "pass_through", in: "query", schema: { type: "object", properties: { k: { type: "string" } } } },
206
+ { name: "limit", in: "query", schema: { type: "integer" } },
207
+ ],
208
+ responses: okJson("#/components/schemas/Invoice"),
209
+ },
210
+ },
211
+ },
212
+ {
213
+ Invoice: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
214
+ InvoicesFilter: { type: "object", properties: { number: { type: "string" } } },
215
+ InvoicesSort: { type: "object", properties: { by: { type: "string" } } },
216
+ },
217
+ );
218
+ const ep = toEndpoints(d).find((e) => e.path === "/accounting/invoices");
219
+ expect(ep).toBeDefined();
220
+ // No longer reported as an unexposed/dropped operation.
221
+ expect(unsupportedOperations(d).some((u) => u.path === "/accounting/invoices")).toBe(false);
222
+ // The full query is preserved — both object params AND the primitive.
223
+ const q = ep!.query as { type: string; properties: Record<string, unknown> };
224
+ expect(q).not.toBeNull();
225
+ expect(Object.keys(q.properties).sort()).toEqual(["filter", "limit", "pass_through", "sort"]);
226
+ });
227
+
228
+ it("captures accessor, method, and path for every supported operation", () => {
229
+ const eps = toEndpoints(
230
+ doc(
231
+ {
232
+ "/items": { get: { operationId: "list", responses: okJson("#/components/schemas/Item") } },
233
+ "/items/{id}": {
234
+ get: {
235
+ operationId: "get",
236
+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
237
+ responses: okJson("#/components/schemas/Item"),
238
+ },
239
+ },
240
+ },
241
+ { Item: { type: "object", properties: { id: { type: "string" } }, required: ["id"] } },
242
+ ),
243
+ );
244
+ expect(eps).toHaveLength(2);
245
+ for (const ep of eps) {
246
+ expect(ep.method).toBeTruthy();
247
+ expect(ep.path).toBeTruthy();
248
+ expect(ep.accessor.length).toBeGreaterThan(0);
249
+ }
250
+ });
251
+ });
@@ -0,0 +1,343 @@
1
+ import { OpenApi } from "@typia/interface";
2
+ import { HttpMigration, OpenApiTypeChecker } from "@typia/utils";
3
+
4
+ import { normalizeForNestia } from "./normalizeForNestia";
5
+
6
+ /**
7
+ * Swagger-native endpoint model — Step toward "swagger만 읽고 정확하게".
8
+ *
9
+ * Built directly from nestia's `HttpMigration.application(doc).routes`, the
10
+ * single source of truth that already carries accessor + query + path params +
11
+ * body + response for EVERY operation. Replaces the former
12
+ * `invertOpenApiDocument`, whose `r.query === null` filter silently dropped
13
+ * every query-string endpoint (search / filter / pagination) — see the bug
14
+ * this migration fixes.
15
+ *
16
+ * Shape mirrors the former endpoint model so the orchestrators change minimally
17
+ * (`document.operations` → `toEndpoints(document)`), plus the one field the old
18
+ * model could not express: {@link IAutoViewEndpoint.query}.
19
+ *
20
+ * Schemas are typed as `OpenApi.IJsonSchema` purely for structural reuse
21
+ * — at runtime they ARE standard OpenAPI schemas (the old invert did the same
22
+ * `as any` cast), so the existing `describeEndpoint*` / `renderJsonSchema`
23
+ * helpers keep working unchanged.
24
+ */
25
+ export interface IAutoViewEndpoint {
26
+ method: string;
27
+ path: string;
28
+
29
+ /** SDK function path nestia assigns, e.g. `["items","search","get"]`. */
30
+ accessor: string[];
31
+
32
+ /** Last accessor segment — the SDK function name. */
33
+ name: string;
34
+
35
+ /** Human description (summary + description merged). */
36
+ description: string;
37
+
38
+ /** Path parameters (primitive). */
39
+ parameters: IAutoViewEndpoint.IParameter[];
40
+
41
+ /**
42
+ * Query-string schema as a single object, or `null` when the endpoint has no
43
+ * query. THIS is what the old AutoBE model could not hold.
44
+ */
45
+ query: OpenApi.IJsonSchema | null;
46
+
47
+ /** Request body reference, or `null`. */
48
+ requestBody: IAutoViewEndpoint.IBody | null;
49
+
50
+ /** Response body reference, or `null`. */
51
+ responseBody: IAutoViewEndpoint.IBody | null;
52
+
53
+ /**
54
+ * The raw success response schema, captured even when {@link responseBody} is
55
+ * `null` because the response is an INLINE object (not a named `$ref`) — e.g.
56
+ * DigitalOcean's `{ databases: [...] }`. Lets the consumability analysis
57
+ * introspect inline collection responses without changing the frontend path
58
+ * (which keys off the named {@link responseBody.typeName}).
59
+ */
60
+ responseSchema: OpenApi.IJsonSchema | null;
61
+ }
62
+ export namespace IAutoViewEndpoint {
63
+ export interface IParameter {
64
+ name: string;
65
+ description: string;
66
+ schema: OpenApi.IJsonSchema;
67
+ }
68
+ export interface IBody {
69
+ description: string;
70
+ /**
71
+ * Component schema name. For an array response (`Pet[]`) this is the
72
+ * ELEMENT type (`Pet`) with {@link isArray} set — so a list screen knows
73
+ * its row type and can derive table columns deterministically.
74
+ */
75
+ typeName: string;
76
+ /** True when the payload is an array of `typeName` (list response). */
77
+ isArray: boolean;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Convert a standard OpenAPI document into AutoView's endpoint list — every
83
+ * operation, query endpoints included.
84
+ */
85
+ /** A segment usable as a JS property in `api.functional.<seg>`. */
86
+ const JS_IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
87
+
88
+ /**
89
+ * True when every accessor segment is a valid JS identifier, so the generator
90
+ * can emit `api.functional.<a>.<b>(…)`. Some real swaggers (Box) use `#`
91
+ * fragments in their paths (`/shared_items#folders`, `/schema#add`), which
92
+ * nestia turns into accessor segments containing `#` — unusable as a dotted
93
+ * property and a hard syntax error in the generated TSX. Such operations are
94
+ * dropped from {@link toEndpoints} and surfaced by {@link unsupportedOperations}.
95
+ */
96
+ export function isRenderableAccessor(accessor: string[]): boolean {
97
+ return accessor.length > 0 && accessor.every((s) => JS_IDENT.test(s));
98
+ }
99
+
100
+ export function toEndpoints(document: OpenApi.IDocument): IAutoViewEndpoint[] {
101
+ const app = HttpMigration.application(normalizeForNestia(document));
102
+ return app.routes
103
+ .filter((r) => isRenderableAccessor(r.accessor))
104
+ .map((r): IAutoViewEndpoint => {
105
+ const body = resolveBody(r.body);
106
+ const successPart = pickSuccess(r);
107
+ const success = resolveBody(successPart);
108
+ // Read description and query from the UNTOUCHED original operation, not the
109
+ // relaxed one HttpMigration saw — the relaxed copy may have shed some object
110
+ // query params just to let the route compose (see normalizeForNestia).
111
+ const operation = originalOperation(document, r.method, r.path) ?? r.operation();
112
+ return {
113
+ method: r.method,
114
+ path: r.path,
115
+ accessor: r.accessor,
116
+ name: r.accessor.at(-1) ?? r.path,
117
+ description: writeDescription(operation) ?? "",
118
+ parameters: r.parameters.map((p) => ({
119
+ name: p.key,
120
+ description: p.parameter().description ?? "",
121
+ // biome-ignore lint: structurally an OpenAPI schema, reused as-is
122
+ schema: p.schema as unknown as OpenApi.IJsonSchema,
123
+ })),
124
+ query: buildQuerySchema(operation, document),
125
+ requestBody: body,
126
+ responseBody: success,
127
+ responseSchema: successPart
128
+ ? // biome-ignore lint: structurally an OpenAPI schema, reused as-is
129
+ (successPart.schema as unknown as OpenApi.IJsonSchema)
130
+ : null,
131
+ };
132
+ });
133
+ }
134
+
135
+ /** Look up the source operation in the original document by method + path. */
136
+ function originalOperation(
137
+ document: OpenApi.IDocument,
138
+ method: string,
139
+ path: string,
140
+ ): OpenApi.IOperation | undefined {
141
+ const item = (document.paths ?? {})[path] as
142
+ | Record<string, OpenApi.IOperation>
143
+ | undefined;
144
+ return item?.[method];
145
+ }
146
+
147
+ /**
148
+ * Build the query-object schema from the raw operation's `in: query`
149
+ * parameters, as an INLINE object (`{ type, properties, required }`).
150
+ *
151
+ * nestia's `route.query.schema` is a `$ref` to a synthetic `*.GetQuery` type
152
+ * that is registered in NEITHER the original document NOR the migration's own
153
+ * document — a dead reference. Resolving it yields nothing, so a list call's
154
+ * defaults came out empty (`query: {}`) and the server 400'd on a required
155
+ * param (petstore's `status`). Reconstructing the schema from the source
156
+ * parameters keeps it inline and resolvable by every consumer, and preserves
157
+ * each param's `required` flag, `enum`, and `default`.
158
+ */
159
+ function buildQuerySchema(
160
+ operation: OpenApi.IOperation | undefined,
161
+ document: OpenApi.IDocument,
162
+ ): OpenApi.IJsonSchema | null {
163
+ const raw = operation?.parameters ?? [];
164
+ const properties: Record<string, OpenApi.IJsonSchema> = {};
165
+ const required: string[] = [];
166
+ for (const entry of raw) {
167
+ const p = derefParameter(entry, document);
168
+ if (p === undefined || p.in !== "query") continue;
169
+ properties[p.name] = (p.schema as OpenApi.IJsonSchema | undefined) ?? {};
170
+ if (p.required === true) required.push(p.name);
171
+ }
172
+ if (Object.keys(properties).length === 0) return null;
173
+ // biome-ignore lint: structurally an OpenAPI object schema
174
+ return { type: "object", properties, required } as OpenApi.IJsonSchema;
175
+ }
176
+
177
+ interface IRawParameter {
178
+ name: string;
179
+ in: string;
180
+ required?: boolean;
181
+ schema?: unknown;
182
+ }
183
+
184
+ /** Resolve a `$ref` parameter against `components.parameters`; pass others through. */
185
+ function derefParameter(
186
+ entry: unknown,
187
+ document: OpenApi.IDocument,
188
+ ): IRawParameter | undefined {
189
+ if (entry !== null && typeof entry === "object" && "$ref" in entry) {
190
+ const ref = (entry as { $ref: unknown }).$ref;
191
+ if (typeof ref !== "string") return undefined;
192
+ const name = ref.split("/").pop();
193
+ const params = (document.components as { parameters?: Record<string, unknown> } | undefined)
194
+ ?.parameters;
195
+ const target = name !== undefined ? params?.[name] : undefined;
196
+ return target as IRawParameter | undefined;
197
+ }
198
+ return entry as IRawParameter | undefined;
199
+ }
200
+
201
+ /** 2xx status codes whose JSON body is the success payload, in preference order. */
202
+ const SUCCESS_CODES = ["200", "201", "202", "203", "206"] as const;
203
+
204
+ /**
205
+ * Pick the real success-response body for a route.
206
+ *
207
+ * nestia's `HttpMigration` only recognizes `200`/`201` as success; when an
208
+ * operation's only 2xx is `202 Accepted` (a body that exists — e.g. Box's
209
+ * `POST /zip_downloads` → `ZipDownload`) or `204 No Content`, it falls back to
210
+ * the `default` response, which is almost always the ERROR schema
211
+ * (`ClientError`). Trusting that makes a screen render error fields (`code`,
212
+ * `message`, `request_id`) as columns and breaks producer tracing.
213
+ *
214
+ * So we trust `route.success` only when its status is genuinely 2xx; otherwise
215
+ * we re-pick from the raw operation's 2xx responses ourselves. A `204` (or any
216
+ * 2xx with no JSON body) correctly yields `null` — no body, not an error body.
217
+ *
218
+ * Trusting nestia when it IS 2xx matters: nestia promotes an inline success body
219
+ * into a named component (`IApiItems.PostResponse`), which the re-pick path (raw
220
+ * inline object, no `$ref`) cannot reproduce. The status it reports is a STRING
221
+ * (`"201"`, `"default"`), so we parse it numerically.
222
+ */
223
+ function pickSuccess(route: {
224
+ success?: IBodyPart | null;
225
+ operation: () => OpenApi.IOperation | undefined;
226
+ }): IBodyPart | null {
227
+ const status = Number(
228
+ (route.success as { status?: unknown } | null | undefined)?.status,
229
+ );
230
+ if (Number.isInteger(status) && status >= 200 && status < 300) {
231
+ return route.success ?? null;
232
+ }
233
+ const responses = route.operation()?.responses ?? {};
234
+ for (const code of SUCCESS_CODES) {
235
+ const response = responses[code] as
236
+ | { description?: string; content?: Record<string, { schema?: unknown }> }
237
+ | undefined;
238
+ const schema = response?.content?.["application/json"]?.schema;
239
+ if (schema !== undefined) {
240
+ return {
241
+ type: "application/json",
242
+ schema: schema as OpenApi.IJsonSchema,
243
+ description: () => response?.description,
244
+ };
245
+ }
246
+ }
247
+ return null; // 204 / no JSON body — a genuine empty response, not an error
248
+ }
249
+
250
+ /**
251
+ * Operations `HttpMigration` could not turn into a route at all — typically an
252
+ * unsupported request/response content type (e.g. `application/octet-stream`,
253
+ * raw binary upload/download). These never reach {@link toEndpoints}, so
254
+ * surfacing them is the only way to keep the READ layer from dropping
255
+ * endpoints silently.
256
+ */
257
+ export interface IUnsupportedOperation {
258
+ method: string;
259
+ path: string;
260
+ reason: string;
261
+ }
262
+
263
+ export function unsupportedOperations(
264
+ document: OpenApi.IDocument,
265
+ ): IUnsupportedOperation[] {
266
+ // Same relaxation as toEndpoints, so multi-object-query routes that now
267
+ // compose are not double-counted here as "dropped".
268
+ const app = HttpMigration.application(normalizeForNestia(document));
269
+ const fromErrors = (app.errors ?? []).map((e): IUnsupportedOperation => {
270
+ const messages: string[] = Array.isArray(e.messages) ? e.messages : [];
271
+ return {
272
+ method: (e.method ?? "?").toString().toUpperCase(),
273
+ path: e.path ?? "?",
274
+ reason: messages.join("; ") || "unsupported operation",
275
+ };
276
+ });
277
+ // Routes whose accessor is not a valid JS identifier chain (path fragments
278
+ // like `/shared_items#folders`) are dropped by `toEndpoints` — report them so
279
+ // nothing vanishes silently.
280
+ const fromAccessors = app.routes
281
+ .filter((r) => !isRenderableAccessor(r.accessor))
282
+ .map((r): IUnsupportedOperation => ({
283
+ method: r.method.toUpperCase(),
284
+ path: r.path,
285
+ reason: `Accessor contains a non-identifier segment (${r.accessor.join(".")}) — cannot generate a typed SDK call.`,
286
+ }));
287
+ return [...fromErrors, ...fromAccessors];
288
+ }
289
+
290
+ interface IBodyPart {
291
+ type: string;
292
+ schema: OpenApi.IJsonSchema;
293
+ description: () => string | undefined;
294
+ }
295
+
296
+ /**
297
+ * Resolve a request/response body part into the element type + array flag.
298
+ * Handles both a named reference (`{ $ref: Pet }` → `Pet`, single) and an
299
+ * array of references (`{ type: array, items: { $ref: Pet } }` → `Pet`,
300
+ * isArray) — the latter is the list-response shape the old code dropped,
301
+ * leaving table screens with no deterministic column source.
302
+ */
303
+ function resolveBody(
304
+ part: IBodyPart | null | undefined,
305
+ ): IAutoViewEndpoint.IBody | null {
306
+ if (!part || part.type !== "application/json") return null;
307
+ const schema = part.schema;
308
+ if (OpenApiTypeChecker.isReference(schema)) {
309
+ return {
310
+ description: part.description() ?? "",
311
+ typeName: refName(schema.$ref),
312
+ isArray: false,
313
+ };
314
+ }
315
+ if (OpenApiTypeChecker.isArray(schema)) {
316
+ const items = schema.items;
317
+ if (OpenApiTypeChecker.isReference(items)) {
318
+ return {
319
+ description: part.description() ?? "",
320
+ typeName: refName(items.$ref),
321
+ isArray: true,
322
+ };
323
+ }
324
+ }
325
+ return null;
326
+ }
327
+
328
+ function refName($ref: string): string {
329
+ return $ref.split("/").pop()!;
330
+ }
331
+
332
+ function writeDescription(
333
+ operation: OpenApi.IOperation | undefined,
334
+ ): string | undefined {
335
+ if (operation === undefined) return undefined;
336
+ if (operation.summary === undefined && operation.description === undefined)
337
+ return undefined;
338
+ if (operation.summary === undefined) return operation.description;
339
+ if (operation.description === undefined) return operation.summary;
340
+ if (operation.description.startsWith(operation.summary))
341
+ return operation.description;
342
+ return `${operation.summary}${operation.summary.endsWith(".") ? "" : "."}\n\n${operation.description}`;
343
+ }