openapi-typesafe 1.0.0-rc.0 → 1.0.0-rc.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.
Files changed (404) hide show
  1. package/dist/adapters/redocly-loader/redocly-config-source.cjs +21 -2
  2. package/dist/adapters/redocly-loader/redocly-config-source.cjs.map +1 -1
  3. package/dist/adapters/redocly-loader/redocly-config-source.mjs +22 -3
  4. package/dist/adapters/redocly-loader/redocly-config-source.mjs.map +1 -1
  5. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +15 -5
  6. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  7. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +15 -5
  8. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  9. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs +3 -0
  10. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs.map +1 -1
  11. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs +3 -0
  12. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs.map +1 -1
  13. package/dist/adapters/type-ast-bridge/typescript-to-ir.cjs +15 -9
  14. package/dist/adapters/type-ast-bridge/typescript-to-ir.cjs.map +1 -1
  15. package/dist/adapters/type-ast-bridge/typescript-to-ir.mjs +15 -9
  16. package/dist/adapters/type-ast-bridge/typescript-to-ir.mjs.map +1 -1
  17. package/dist/api/discriminator-conflict-warnings.cjs +25 -0
  18. package/dist/api/discriminator-conflict-warnings.cjs.map +1 -0
  19. package/dist/api/discriminator-conflict-warnings.mjs +23 -0
  20. package/dist/api/discriminator-conflict-warnings.mjs.map +1 -0
  21. package/dist/api/discriminator-enum-names.cjs +19 -0
  22. package/dist/api/discriminator-enum-names.cjs.map +1 -0
  23. package/dist/api/discriminator-enum-names.mjs +17 -0
  24. package/dist/api/discriminator-enum-names.mjs.map +1 -0
  25. package/dist/api/discriminator-enum-parent.cjs +59 -0
  26. package/dist/api/discriminator-enum-parent.cjs.map +1 -0
  27. package/dist/api/discriminator-enum-parent.mjs +57 -0
  28. package/dist/api/discriminator-enum-parent.mjs.map +1 -0
  29. package/dist/api/discriminator-enum.cjs +25 -0
  30. package/dist/api/discriminator-enum.cjs.map +1 -1
  31. package/dist/api/discriminator-enum.mjs +25 -1
  32. package/dist/api/discriminator-enum.mjs.map +1 -1
  33. package/dist/api/discriminator-other-owners.cjs +67 -0
  34. package/dist/api/discriminator-other-owners.cjs.map +1 -0
  35. package/dist/api/discriminator-other-owners.mjs +64 -0
  36. package/dist/api/discriminator-other-owners.mjs.map +1 -0
  37. package/dist/api/discriminator-patch.cjs +88 -0
  38. package/dist/api/discriminator-patch.cjs.map +1 -0
  39. package/dist/api/discriminator-patch.mjs +82 -0
  40. package/dist/api/discriminator-patch.mjs.map +1 -0
  41. package/dist/api/discriminator-scan.cjs +50 -65
  42. package/dist/api/discriminator-scan.cjs.map +1 -1
  43. package/dist/api/discriminator-scan.mjs +44 -59
  44. package/dist/api/discriminator-scan.mjs.map +1 -1
  45. package/dist/api/document-run.cjs +18 -3
  46. package/dist/api/document-run.cjs.map +1 -1
  47. package/dist/api/document-run.mjs +19 -4
  48. package/dist/api/document-run.mjs.map +1 -1
  49. package/dist/api/enum-values-warnings.cjs +16 -0
  50. package/dist/api/enum-values-warnings.cjs.map +1 -0
  51. package/dist/api/enum-values-warnings.mjs +14 -0
  52. package/dist/api/enum-values-warnings.mjs.map +1 -0
  53. package/dist/api/path-template-warnings.cjs +21 -0
  54. package/dist/api/path-template-warnings.cjs.map +1 -0
  55. package/dist/api/path-template-warnings.mjs +19 -0
  56. package/dist/api/path-template-warnings.mjs.map +1 -0
  57. package/dist/api/transform-context.cjs +3 -1
  58. package/dist/api/transform-context.cjs.map +1 -1
  59. package/dist/api/transform-context.mjs +3 -1
  60. package/dist/api/transform-context.mjs.map +1 -1
  61. package/dist/api/transform-schema.cjs +52 -3
  62. package/dist/api/transform-schema.cjs.map +1 -1
  63. package/dist/api/transform-schema.mjs +53 -4
  64. package/dist/api/transform-schema.mjs.map +1 -1
  65. package/dist/api/unsupported-feature-warnings.cjs +6 -1
  66. package/dist/api/unsupported-feature-warnings.cjs.map +1 -1
  67. package/dist/api/unsupported-feature-warnings.mjs +6 -1
  68. package/dist/api/unsupported-feature-warnings.mjs.map +1 -1
  69. package/dist/application/print/type-printer.cjs +5 -0
  70. package/dist/application/print/type-printer.cjs.map +1 -1
  71. package/dist/application/print/type-printer.mjs +5 -0
  72. package/dist/application/print/type-printer.mjs.map +1 -1
  73. package/dist/application/transform/document/assignability.cjs +144 -0
  74. package/dist/application/transform/document/assignability.cjs.map +1 -0
  75. package/dist/application/transform/document/assignability.mjs +138 -0
  76. package/dist/application/transform/document/assignability.mjs.map +1 -0
  77. package/dist/application/transform/document/components.cjs +2 -2
  78. package/dist/application/transform/document/components.cjs.map +1 -1
  79. package/dist/application/transform/document/components.mjs +4 -4
  80. package/dist/application/transform/document/components.mjs.map +1 -1
  81. package/dist/application/transform/document/declared-types.cjs +66 -0
  82. package/dist/application/transform/document/declared-types.cjs.map +1 -0
  83. package/dist/application/transform/document/declared-types.mjs +63 -0
  84. package/dist/application/transform/document/declared-types.mjs.map +1 -0
  85. package/dist/application/transform/document/document.cjs +32 -9
  86. package/dist/application/transform/document/document.cjs.map +1 -1
  87. package/dist/application/transform/document/document.mjs +34 -11
  88. package/dist/application/transform/document/document.mjs.map +1 -1
  89. package/dist/application/transform/document/exploded-query.cjs +46 -0
  90. package/dist/application/transform/document/exploded-query.cjs.map +1 -0
  91. package/dist/application/transform/document/exploded-query.mjs +43 -0
  92. package/dist/application/transform/document/exploded-query.mjs.map +1 -0
  93. package/dist/application/transform/document/global-type-references.cjs +63 -0
  94. package/dist/application/transform/document/global-type-references.cjs.map +1 -0
  95. package/dist/application/transform/document/global-type-references.mjs +61 -0
  96. package/dist/application/transform/document/global-type-references.mjs.map +1 -0
  97. package/dist/application/transform/document/list-assignability.cjs +54 -0
  98. package/dist/application/transform/document/list-assignability.cjs.map +1 -0
  99. package/dist/application/transform/document/list-assignability.mjs +52 -0
  100. package/dist/application/transform/document/list-assignability.mjs.map +1 -0
  101. package/dist/application/transform/document/parameters.cjs +40 -8
  102. package/dist/application/transform/document/parameters.cjs.map +1 -1
  103. package/dist/application/transform/document/parameters.mjs +41 -9
  104. package/dist/application/transform/document/parameters.mjs.map +1 -1
  105. package/dist/application/transform/document/path-key-overlaps.cjs +83 -33
  106. package/dist/application/transform/document/path-key-overlaps.cjs.map +1 -1
  107. package/dist/application/transform/document/path-key-overlaps.mjs +83 -33
  108. package/dist/application/transform/document/path-key-overlaps.mjs.map +1 -1
  109. package/dist/application/transform/document/path-template-replace.cjs +48 -0
  110. package/dist/application/transform/document/path-template-replace.cjs.map +1 -0
  111. package/dist/application/transform/document/path-template-replace.mjs +45 -0
  112. package/dist/application/transform/document/path-template-replace.mjs.map +1 -0
  113. package/dist/application/transform/document/paths.cjs +54 -28
  114. package/dist/application/transform/document/paths.cjs.map +1 -1
  115. package/dist/application/transform/document/paths.mjs +53 -29
  116. package/dist/application/transform/document/paths.mjs.map +1 -1
  117. package/dist/application/transform/document/root-type-aliases.cjs +15 -14
  118. package/dist/application/transform/document/root-type-aliases.cjs.map +1 -1
  119. package/dist/application/transform/document/root-type-aliases.mjs +15 -13
  120. package/dist/application/transform/document/root-type-aliases.mjs.map +1 -1
  121. package/dist/application/transform/document/template-key-match.cjs +128 -0
  122. package/dist/application/transform/document/template-key-match.cjs.map +1 -0
  123. package/dist/application/transform/document/template-key-match.mjs +125 -0
  124. package/dist/application/transform/document/template-key-match.mjs.map +1 -0
  125. package/dist/application/transform/document/type-assignability.cjs +192 -0
  126. package/dist/application/transform/document/type-assignability.cjs.map +1 -0
  127. package/dist/application/transform/document/type-assignability.mjs +190 -0
  128. package/dist/application/transform/document/type-assignability.mjs.map +1 -0
  129. package/dist/application/transform/document/type-key.cjs +70 -0
  130. package/dist/application/transform/document/type-key.cjs.map +1 -0
  131. package/dist/application/transform/document/type-key.mjs +68 -0
  132. package/dist/application/transform/document/type-key.mjs.map +1 -0
  133. package/dist/application/transform/document/type-resolution.cjs +131 -0
  134. package/dist/application/transform/document/type-resolution.cjs.map +1 -0
  135. package/dist/application/transform/document/type-resolution.mjs +125 -0
  136. package/dist/application/transform/document/type-resolution.mjs.map +1 -0
  137. package/dist/application/transform/footer/document-footer.cjs +66 -4
  138. package/dist/application/transform/footer/document-footer.cjs.map +1 -1
  139. package/dist/application/transform/footer/document-footer.mjs +67 -5
  140. package/dist/application/transform/footer/document-footer.mjs.map +1 -1
  141. package/dist/application/transform/footer/enum-naming.cjs +17 -0
  142. package/dist/application/transform/footer/enum-naming.cjs.map +1 -1
  143. package/dist/application/transform/footer/enum-naming.mjs +17 -1
  144. package/dist/application/transform/footer/enum-naming.mjs.map +1 -1
  145. package/dist/application/transform/footer/enum-values-check.cjs +129 -0
  146. package/dist/application/transform/footer/enum-values-check.cjs.map +1 -0
  147. package/dist/application/transform/footer/enum-values-check.mjs +127 -0
  148. package/dist/application/transform/footer/enum-values-check.mjs.map +1 -0
  149. package/dist/application/transform/footer/evaluation.cjs +33 -0
  150. package/dist/application/transform/footer/evaluation.cjs.map +1 -0
  151. package/dist/application/transform/footer/evaluation.mjs +26 -0
  152. package/dist/application/transform/footer/evaluation.mjs.map +1 -0
  153. package/dist/application/transform/footer/shared-enum-names.cjs +60 -0
  154. package/dist/application/transform/footer/shared-enum-names.cjs.map +1 -0
  155. package/dist/application/transform/footer/shared-enum-names.mjs +57 -0
  156. package/dist/application/transform/footer/shared-enum-names.mjs.map +1 -0
  157. package/dist/application/transform/footer/type-evaluation.cjs +246 -0
  158. package/dist/application/transform/footer/type-evaluation.cjs.map +1 -0
  159. package/dist/application/transform/footer/type-evaluation.mjs +244 -0
  160. package/dist/application/transform/footer/type-evaluation.mjs.map +1 -0
  161. package/dist/application/transform/footer/type-rules.cjs +172 -0
  162. package/dist/application/transform/footer/type-rules.cjs.map +1 -0
  163. package/dist/application/transform/footer/type-rules.mjs +166 -0
  164. package/dist/application/transform/footer/type-rules.mjs.map +1 -0
  165. package/dist/application/transform/ref-pointer/generated-location.cjs +138 -0
  166. package/dist/application/transform/ref-pointer/generated-location.cjs.map +1 -0
  167. package/dist/application/transform/ref-pointer/generated-location.mjs +136 -0
  168. package/dist/application/transform/ref-pointer/generated-location.mjs.map +1 -0
  169. package/dist/application/transform/ref-pointer/generated-shape.cjs +171 -0
  170. package/dist/application/transform/ref-pointer/generated-shape.cjs.map +1 -0
  171. package/dist/application/transform/ref-pointer/generated-shape.mjs +162 -0
  172. package/dist/application/transform/ref-pointer/generated-shape.mjs.map +1 -0
  173. package/dist/application/transform/ref-pointer/inline-cycles.cjs +44 -0
  174. package/dist/application/transform/ref-pointer/inline-cycles.cjs.map +1 -0
  175. package/dist/application/transform/ref-pointer/inline-cycles.mjs +42 -0
  176. package/dist/application/transform/ref-pointer/inline-cycles.mjs.map +1 -0
  177. package/dist/application/transform/ref-pointer/original-index.cjs +62 -0
  178. package/dist/application/transform/ref-pointer/original-index.cjs.map +1 -0
  179. package/dist/application/transform/ref-pointer/original-index.mjs +60 -0
  180. package/dist/application/transform/ref-pointer/original-index.mjs.map +1 -0
  181. package/dist/application/transform/ref-pointer/pointer-type.cjs +60 -0
  182. package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -0
  183. package/dist/application/transform/ref-pointer/pointer-type.mjs +57 -0
  184. package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -0
  185. package/dist/application/transform/ref-pointer/pointer-walk.cjs +66 -0
  186. package/dist/application/transform/ref-pointer/pointer-walk.cjs.map +1 -0
  187. package/dist/application/transform/ref-pointer/pointer-walk.mjs +56 -0
  188. package/dist/application/transform/ref-pointer/pointer-walk.mjs.map +1 -0
  189. package/dist/application/transform/ref-pointer/typed-walk.cjs +124 -0
  190. package/dist/application/transform/ref-pointer/typed-walk.cjs.map +1 -0
  191. package/dist/application/transform/ref-pointer/typed-walk.mjs +122 -0
  192. package/dist/application/transform/ref-pointer/typed-walk.mjs.map +1 -0
  193. package/dist/application/transform/ref-type.cjs +7 -2
  194. package/dist/application/transform/ref-type.cjs.map +1 -1
  195. package/dist/application/transform/ref-type.mjs +3 -2
  196. package/dist/application/transform/ref-type.mjs.map +1 -1
  197. package/dist/application/transform/schema/access-flags.cjs +38 -0
  198. package/dist/application/transform/schema/access-flags.cjs.map +1 -0
  199. package/dist/application/transform/schema/access-flags.mjs +36 -0
  200. package/dist/application/transform/schema/access-flags.mjs.map +1 -0
  201. package/dist/application/transform/schema/array-type.cjs +32 -1
  202. package/dist/application/transform/schema/array-type.cjs.map +1 -1
  203. package/dist/application/transform/schema/array-type.mjs +33 -2
  204. package/dist/application/transform/schema/array-type.mjs.map +1 -1
  205. package/dist/application/transform/schema/composition-type.cjs +50 -15
  206. package/dist/application/transform/schema/composition-type.cjs.map +1 -1
  207. package/dist/application/transform/schema/composition-type.mjs +50 -15
  208. package/dist/application/transform/schema/composition-type.mjs.map +1 -1
  209. package/dist/application/transform/schema/core-type.cjs +3 -1
  210. package/dist/application/transform/schema/core-type.cjs.map +1 -1
  211. package/dist/application/transform/schema/core-type.mjs +3 -1
  212. package/dist/application/transform/schema/core-type.mjs.map +1 -1
  213. package/dist/application/transform/schema/discriminated-union.cjs +30 -0
  214. package/dist/application/transform/schema/discriminated-union.cjs.map +1 -0
  215. package/dist/application/transform/schema/discriminated-union.mjs +27 -0
  216. package/dist/application/transform/schema/discriminated-union.mjs.map +1 -0
  217. package/dist/application/transform/schema/discriminator-property.cjs +3 -2
  218. package/dist/application/transform/schema/discriminator-property.cjs.map +1 -1
  219. package/dist/application/transform/schema/discriminator-property.mjs +3 -2
  220. package/dist/application/transform/schema/discriminator-property.mjs.map +1 -1
  221. package/dist/application/transform/schema/enum-rule.cjs +66 -10
  222. package/dist/application/transform/schema/enum-rule.cjs.map +1 -1
  223. package/dist/application/transform/schema/enum-rule.mjs +67 -12
  224. package/dist/application/transform/schema/enum-rule.mjs.map +1 -1
  225. package/dist/application/transform/schema/inherited-discriminator.cjs +29 -0
  226. package/dist/application/transform/schema/inherited-discriminator.cjs.map +1 -0
  227. package/dist/application/transform/schema/inherited-discriminator.mjs +26 -0
  228. package/dist/application/transform/schema/inherited-discriminator.mjs.map +1 -0
  229. package/dist/application/transform/schema/object-type.cjs +38 -25
  230. package/dist/application/transform/schema/object-type.cjs.map +1 -1
  231. package/dist/application/transform/schema/object-type.mjs +39 -27
  232. package/dist/application/transform/schema/object-type.mjs.map +1 -1
  233. package/dist/application/transform/schema/required-variants.cjs +45 -0
  234. package/dist/application/transform/schema/required-variants.cjs.map +1 -0
  235. package/dist/application/transform/schema/required-variants.mjs +42 -0
  236. package/dist/application/transform/schema/required-variants.mjs.map +1 -0
  237. package/dist/application/transform/schema/schema-transform.cjs +2 -2
  238. package/dist/application/transform/schema/schema-transform.cjs.map +1 -1
  239. package/dist/application/transform/schema/schema-transform.mjs +2 -2
  240. package/dist/application/transform/schema/schema-transform.mjs.map +1 -1
  241. package/dist/application/transform/schema-comment.cjs +51 -5
  242. package/dist/application/transform/schema-comment.cjs.map +1 -1
  243. package/dist/application/transform/schema-comment.mjs +50 -6
  244. package/dist/application/transform/schema-comment.mjs.map +1 -1
  245. package/dist/application/transform/type-combinators.mjs +1 -1
  246. package/dist/cli/cli-arguments.cjs +11 -3
  247. package/dist/cli/cli-arguments.cjs.map +1 -1
  248. package/dist/cli/cli-arguments.mjs +11 -3
  249. package/dist/cli/cli-arguments.mjs.map +1 -1
  250. package/dist/cli/cli-errors.cjs +3 -2
  251. package/dist/cli/cli-errors.cjs.map +1 -1
  252. package/dist/cli/cli-errors.mjs +3 -2
  253. package/dist/cli/cli-errors.mjs.map +1 -1
  254. package/dist/cli/cli-output.cjs +8 -3
  255. package/dist/cli/cli-output.cjs.map +1 -1
  256. package/dist/cli/cli-output.mjs +9 -4
  257. package/dist/cli/cli-output.mjs.map +1 -1
  258. package/dist/cli/main.cjs +5 -0
  259. package/dist/cli/main.cjs.map +1 -1
  260. package/dist/cli/main.mjs +5 -0
  261. package/dist/cli/main.mjs.map +1 -1
  262. package/dist/cli/redocly-apis.cjs +7 -3
  263. package/dist/cli/redocly-apis.cjs.map +1 -1
  264. package/dist/cli/redocly-apis.mjs +8 -4
  265. package/dist/cli/redocly-apis.mjs.map +1 -1
  266. package/dist/cli/ref-limits.cjs +1 -1
  267. package/dist/cli/ref-limits.cjs.map +1 -1
  268. package/dist/cli/ref-limits.mjs +2 -2
  269. package/dist/cli/ref-limits.mjs.map +1 -1
  270. package/dist/cli/run-cli.cjs +15 -2
  271. package/dist/cli/run-cli.cjs.map +1 -1
  272. package/dist/cli/run-cli.mjs +16 -3
  273. package/dist/cli/run-cli.mjs.map +1 -1
  274. package/dist/domain/errors.cjs +2 -0
  275. package/dist/domain/errors.cjs.map +1 -1
  276. package/dist/domain/errors.mjs +2 -0
  277. package/dist/domain/errors.mjs.map +1 -1
  278. package/dist/domain/ir/constructors.cjs +5 -1
  279. package/dist/domain/ir/constructors.cjs.map +1 -1
  280. package/dist/domain/ir/constructors.mjs +5 -2
  281. package/dist/domain/ir/constructors.mjs.map +1 -1
  282. package/dist/domain/openapi/admitted-values.cjs +75 -0
  283. package/dist/domain/openapi/admitted-values.cjs.map +1 -0
  284. package/dist/domain/openapi/admitted-values.mjs +72 -0
  285. package/dist/domain/openapi/admitted-values.mjs.map +1 -0
  286. package/dist/domain/openapi/discriminated-union.cjs +68 -0
  287. package/dist/domain/openapi/discriminated-union.cjs.map +1 -0
  288. package/dist/domain/openapi/discriminated-union.mjs +62 -0
  289. package/dist/domain/openapi/discriminated-union.mjs.map +1 -0
  290. package/dist/domain/openapi/discriminator-conflicts.cjs +143 -0
  291. package/dist/domain/openapi/discriminator-conflicts.cjs.map +1 -0
  292. package/dist/domain/openapi/discriminator-conflicts.mjs +141 -0
  293. package/dist/domain/openapi/discriminator-conflicts.mjs.map +1 -0
  294. package/dist/domain/openapi/parameter-object.cjs.map +1 -1
  295. package/dist/domain/openapi/parameter-object.mjs.map +1 -1
  296. package/dist/domain/openapi/path-templates.cjs +23 -0
  297. package/dist/domain/openapi/path-templates.cjs.map +1 -0
  298. package/dist/domain/openapi/path-templates.mjs +21 -0
  299. package/dist/domain/openapi/path-templates.mjs.map +1 -0
  300. package/dist/domain/output-fixes.cjs +36 -1
  301. package/dist/domain/output-fixes.cjs.map +1 -1
  302. package/dist/domain/output-fixes.mjs +36 -1
  303. package/dist/domain/output-fixes.mjs.map +1 -1
  304. package/dist/domain/warnings.cjs +15 -0
  305. package/dist/domain/warnings.cjs.map +1 -0
  306. package/dist/domain/warnings.mjs +12 -0
  307. package/dist/domain/warnings.mjs.map +1 -0
  308. package/dist/index.d.cts +29 -0
  309. package/dist/index.d.mts +29 -0
  310. package/dist/index.d.ts +29 -0
  311. package/dist/transform/components-object.cjs +2 -1
  312. package/dist/transform/components-object.cjs.map +1 -1
  313. package/dist/transform/components-object.mjs +2 -1
  314. package/dist/transform/components-object.mjs.map +1 -1
  315. package/package.json +1 -1
  316. package/src/adapters/redocly-loader/redocly-config-source.ts +41 -6
  317. package/src/adapters/redocly-loader/redocly-schema-loader.ts +22 -6
  318. package/src/adapters/type-ast-bridge/ir-to-typescript.ts +3 -0
  319. package/src/adapters/type-ast-bridge/typescript-to-ir.ts +26 -12
  320. package/src/api/discriminator-conflict-warnings.ts +43 -0
  321. package/src/api/discriminator-enum-names.ts +26 -0
  322. package/src/api/discriminator-enum-parent.ts +129 -0
  323. package/src/api/discriminator-enum.ts +39 -0
  324. package/src/api/discriminator-other-owners.ts +97 -0
  325. package/src/api/discriminator-patch.ts +147 -0
  326. package/src/api/discriminator-scan.ts +78 -90
  327. package/src/api/document-run.ts +24 -6
  328. package/src/api/enum-values-warnings.ts +21 -0
  329. package/src/api/path-template-warnings.ts +27 -0
  330. package/src/api/transform-context.ts +7 -1
  331. package/src/api/transform-schema.ts +88 -4
  332. package/src/api/unsupported-feature-warnings.ts +8 -1
  333. package/src/application/ports/redocly-config-source.ts +5 -1
  334. package/src/application/print/type-printer.ts +6 -0
  335. package/src/application/transform/document/assignability.ts +203 -0
  336. package/src/application/transform/document/components.ts +6 -6
  337. package/src/application/transform/document/declared-types.ts +93 -0
  338. package/src/application/transform/document/document.ts +71 -13
  339. package/src/application/transform/document/exploded-query.ts +91 -0
  340. package/src/application/transform/document/global-type-references.ts +78 -0
  341. package/src/application/transform/document/list-assignability.ts +87 -0
  342. package/src/application/transform/document/parameters.ts +76 -11
  343. package/src/application/transform/document/path-key-overlaps.ts +148 -59
  344. package/src/application/transform/document/path-template-replace.ts +90 -0
  345. package/src/application/transform/document/paths.ts +99 -37
  346. package/src/application/transform/document/root-type-aliases.ts +29 -28
  347. package/src/application/transform/document/template-key-match.ts +183 -0
  348. package/src/application/transform/document/type-assignability.ts +268 -0
  349. package/src/application/transform/document/type-key.ts +80 -0
  350. package/src/application/transform/document/type-resolution.ts +163 -0
  351. package/src/application/transform/footer/document-footer.ts +91 -6
  352. package/src/application/transform/footer/enum-naming.ts +29 -0
  353. package/src/application/transform/footer/enum-values-check.ts +171 -0
  354. package/src/application/transform/footer/evaluation.ts +47 -0
  355. package/src/application/transform/footer/shared-enum-names.ts +86 -0
  356. package/src/application/transform/footer/type-evaluation.ts +294 -0
  357. package/src/application/transform/footer/type-rules.ts +269 -0
  358. package/src/application/transform/ref-pointer/generated-location.ts +211 -0
  359. package/src/application/transform/ref-pointer/generated-shape.ts +275 -0
  360. package/src/application/transform/ref-pointer/inline-cycles.ts +56 -0
  361. package/src/application/transform/ref-pointer/original-index.ts +95 -0
  362. package/src/application/transform/ref-pointer/pointer-type.ts +92 -0
  363. package/src/application/transform/ref-pointer/pointer-walk.ts +96 -0
  364. package/src/application/transform/ref-pointer/typed-walk.ts +192 -0
  365. package/src/application/transform/ref-type.ts +11 -6
  366. package/src/application/transform/schema/access-flags.ts +63 -0
  367. package/src/application/transform/schema/array-type.ts +66 -5
  368. package/src/application/transform/schema/composition-type.ts +96 -24
  369. package/src/application/transform/schema/core-type.ts +8 -4
  370. package/src/application/transform/schema/discriminated-union.ts +58 -0
  371. package/src/application/transform/schema/discriminator-property.ts +8 -3
  372. package/src/application/transform/schema/enum-rule.ts +136 -13
  373. package/src/application/transform/schema/inherited-discriminator.ts +54 -0
  374. package/src/application/transform/schema/object-type.ts +51 -33
  375. package/src/application/transform/schema/required-variants.ts +88 -0
  376. package/src/application/transform/schema/schema-context.ts +26 -0
  377. package/src/application/transform/schema/schema-transform.ts +2 -2
  378. package/src/application/transform/schema-comment.ts +84 -4
  379. package/src/cli/cli-arguments.ts +13 -3
  380. package/src/cli/cli-dependencies.ts +5 -0
  381. package/src/cli/cli-errors.ts +4 -2
  382. package/src/cli/cli-output.ts +18 -4
  383. package/src/cli/main.ts +6 -0
  384. package/src/cli/redocly-apis.ts +8 -4
  385. package/src/cli/ref-limits.ts +2 -2
  386. package/src/cli/run-cli.ts +20 -3
  387. package/src/domain/errors.ts +2 -0
  388. package/src/domain/ir/constructors.ts +6 -1
  389. package/src/domain/ir/nodes.ts +2 -1
  390. package/src/domain/openapi/admitted-values.ts +113 -0
  391. package/src/domain/openapi/discriminated-union.ts +109 -0
  392. package/src/domain/openapi/discriminator-conflicts.ts +251 -0
  393. package/src/domain/openapi/parameter-object.ts +4 -0
  394. package/src/domain/openapi/path-templates.ts +31 -0
  395. package/src/domain/openapi/schema-object.ts +2 -0
  396. package/src/domain/output-fixes.ts +43 -2
  397. package/src/domain/warnings.ts +20 -0
  398. package/src/transform/components-object.ts +3 -1
  399. package/src/types/generator-options.ts +11 -0
  400. package/dist/application/transform/document/path-item-conflicts.cjs +0 -51
  401. package/dist/application/transform/document/path-item-conflicts.cjs.map +0 -1
  402. package/dist/application/transform/document/path-item-conflicts.mjs +0 -49
  403. package/dist/application/transform/document/path-item-conflicts.mjs.map +0 -1
  404. package/src/application/transform/document/path-item-conflicts.ts +0 -79
@@ -3,6 +3,7 @@ import {
3
3
  findUnsupportedFeatures,
4
4
  UnsupportedFeature,
5
5
  } from "../domain/openapi/unsupported-features.js";
6
+ import { WARNING_CODES, type WarningCode, warningText } from "../domain/warnings.js";
6
7
 
7
8
  const DOCS_BASE_URL = "https://github.com/openapi-typesafe/openapi-typesafe/blob/main/docs";
8
9
  const SUPPORT_DOCS_URL = `${DOCS_BASE_URL}/introduction.md#openapi-3-2-support`;
@@ -15,12 +16,18 @@ const REASONS: Readonly<Record<UnsupportedFeature, string>> = {
15
16
  "generated types",
16
17
  };
17
18
 
19
+ /** The warning code of each feature. */
20
+ const CODES: Readonly<Record<UnsupportedFeature, WarningCode>> = {
21
+ [UnsupportedFeature.QUERYSTRING_NEXT_TO_QUERY]: WARNING_CODES.QUERYSTRING_NEXT_TO_QUERY,
22
+ };
23
+
18
24
  /**
19
25
  * Warn once per use of an OpenAPI 3.2 feature that the generated types leave out, so a missing
20
26
  * parameter is never silent (CODING_PRINCIPLES §8.7).
21
27
  */
22
28
  export function warnUnsupportedFeatures(document: unknown, logger: Logger): void {
23
29
  for (const { feature, pointer } of findUnsupportedFeatures(document)) {
24
- logger.warn(`${pointer} ${REASONS[feature]}. See ${SUPPORT_DOCS_URL}`);
30
+ const message = `${pointer} ${REASONS[feature]}. See ${SUPPORT_DOCS_URL}`;
31
+ logger.warn(warningText(message, CODES[feature]));
25
32
  }
26
33
  }
@@ -10,7 +10,11 @@ export interface RedoclyConfigSource {
10
10
  * from the working directory. Undefined when there is none.
11
11
  */
12
12
  find(explicitPath: string | undefined): string | undefined;
13
+ /** The config of a file; fails with `CLI_CONFIG_INVALID` when it or its ignore file is bad. */
13
14
  load(configPath: string): Promise<RedoclyConfig>;
14
- /** The config used when there is no file: the minimal rules. */
15
+ /**
16
+ * The config used when there is no file: the minimal rules. Fails with `CLI_CONFIG_INVALID`
17
+ * when the ignore file of the working directory is bad.
18
+ */
15
19
  createDefault(): Promise<RedoclyConfig>;
16
20
  }
@@ -135,6 +135,12 @@ function writeTupleElement(writer: TextWriter, element: IrTupleElement): void {
135
135
  if (element.isRest) {
136
136
  writer.write("...");
137
137
  }
138
+ if (element.isOptional) {
139
+ // TypeScript parenthesizes an optional element as a postfix operand (`(A | B)?`).
140
+ writeTypeIn(writer, element.type, needsParensAsPostfixOperand);
141
+ writer.write("?");
142
+ return;
143
+ }
138
144
  writeType(writer, element.type);
139
145
  }
140
146
 
@@ -0,0 +1,203 @@
1
+ import type { IrObjectType, IrPropertySignature, IrType } from "../../../domain/ir/nodes.js";
2
+ import { indexSignaturesOf, nameOf, propertiesOf } from "./type-resolution.js";
3
+
4
+ /**
5
+ * What the `--path-params-as-types` overlap check knows about one type where another is expected.
6
+ * `assignable` and `rejected` are proofs of what TypeScript decides (`strict`, without
7
+ * `exactOptionalPropertyTypes`); `undecided` is a pair the check does not model, such as a utility
8
+ * type or raw text. Only a rejection changes the output, so an undecided pair keeps the original.
9
+ */
10
+ export type Assignability = "assignable" | "rejected" | "undecided";
11
+
12
+ /**
13
+ * All of the relations: rejected when one is, assignable when each is. The relations are computed
14
+ * lazily, so a rejection stops the rest.
15
+ */
16
+ export function everyOf<T>(
17
+ items: readonly T[],
18
+ relate: (item: T, index: number) => Assignability,
19
+ ): Assignability {
20
+ let result: Assignability = "assignable";
21
+ for (const [index, item] of items.entries()) {
22
+ const each = relate(item, index);
23
+ if (each === "rejected") {
24
+ return "rejected";
25
+ }
26
+ if (each === "undecided") {
27
+ result = "undecided";
28
+ }
29
+ }
30
+ return result;
31
+ }
32
+
33
+ /** One of the relations: assignable when one is, rejected when each is. */
34
+ export function someOf<T>(items: readonly T[], relate: (item: T) => Assignability): Assignability {
35
+ let result: Assignability = "rejected";
36
+ for (const item of items) {
37
+ const each = relate(item);
38
+ if (each === "assignable") {
39
+ return "assignable";
40
+ }
41
+ if (each === "undecided") {
42
+ result = "undecided";
43
+ }
44
+ }
45
+ return result;
46
+ }
47
+
48
+ /** Assignable when the condition holds, rejected when it does not. */
49
+ export function provenBy(condition: boolean): Assignability {
50
+ return condition ? "assignable" : "rejected";
51
+ }
52
+
53
+ /**
54
+ * The kinds of resolved types the check tells apart. A type of one kind is never assignable to a
55
+ * type of another, except that a primitive or a list may fit an object type (`string` fits
56
+ * `{ length: number }`), which the check models only in part. `opaque` is any type it cannot
57
+ * read.
58
+ */
59
+ type TypeCategory = "primitive" | "list" | "object" | "opaque";
60
+
61
+ function categoryOf(type: IrType): TypeCategory {
62
+ switch (type.kind) {
63
+ case "keyword":
64
+ case "stringLiteral":
65
+ case "numberLiteral":
66
+ case "booleanLiteral":
67
+ return "primitive";
68
+ case "array":
69
+ case "tuple":
70
+ case "readonly":
71
+ return "list";
72
+ case "object":
73
+ return "object";
74
+ default:
75
+ return "opaque";
76
+ }
77
+ }
78
+
79
+ /** Keywords that are no object: `{}` rejects them under `strictNullChecks`. */
80
+ const NO_OBJECT_KEYWORDS: ReadonlySet<string> = new Set(["null", "undefined", "unknown", "never"]);
81
+
82
+ /**
83
+ * The members of `ReadonlyArray` and `Array` in the TypeScript libraries up to ESNext. An object
84
+ * type that needs a property of another name is no list; a numeric name may be a tuple element.
85
+ */
86
+ const ARRAY_MEMBER_NAMES: ReadonlySet<string> = new Set([
87
+ "length",
88
+ "toString",
89
+ "toLocaleString",
90
+ "pop",
91
+ "push",
92
+ "concat",
93
+ "join",
94
+ "reverse",
95
+ "shift",
96
+ "slice",
97
+ "sort",
98
+ "splice",
99
+ "unshift",
100
+ "indexOf",
101
+ "lastIndexOf",
102
+ "every",
103
+ "some",
104
+ "forEach",
105
+ "map",
106
+ "filter",
107
+ "reduce",
108
+ "reduceRight",
109
+ "find",
110
+ "findIndex",
111
+ "fill",
112
+ "copyWithin",
113
+ "entries",
114
+ "keys",
115
+ "values",
116
+ "includes",
117
+ "flatMap",
118
+ "flat",
119
+ "at",
120
+ "findLast",
121
+ "findLastIndex",
122
+ "toReversed",
123
+ "toSorted",
124
+ "toSpliced",
125
+ "with",
126
+ ]);
127
+
128
+ const ARRAY_LENGTH = "length";
129
+ /** A tuple element's name: `"0"`, `"1"`. */
130
+ const ELEMENT_NAME_RE = /^\d+$/;
131
+
132
+ function isListMemberName(name: string): boolean {
133
+ return ARRAY_MEMBER_NAMES.has(name) || ELEMENT_NAME_RE.test(name);
134
+ }
135
+
136
+ /**
137
+ * Whether a list fits an object type. It does not when the object needs a property no list has
138
+ * (TS2741), or when the object is a weak type and no list has one of its properties (TS2559).
139
+ */
140
+ function listToObject(target: IrObjectType): Assignability {
141
+ const properties = [...propertiesOf(target).values()];
142
+ const isForeign = (property: IrPropertySignature) => !isListMemberName(nameOf(property));
143
+ const needsForeign = properties.some((property) => !property.isOptional && isForeign(property));
144
+ const isWeak =
145
+ properties.length > 0 &&
146
+ indexSignaturesOf(target).length === 0 &&
147
+ properties.every((property) => property.isOptional);
148
+ return needsForeign || (isWeak && properties.every(isForeign)) ? "rejected" : "undecided";
149
+ }
150
+
151
+ /**
152
+ * Whether a primitive fits an object type: `null`, `undefined` and `unknown` never do; any other
153
+ * may (`{}`, `{ length: number }`), which is not modelled.
154
+ */
155
+ function primitiveToObject(source: IrType): Assignability {
156
+ const isNoObject = source.kind === "keyword" && NO_OBJECT_KEYWORDS.has(source.keyword);
157
+ return isNoObject ? "rejected" : "undecided";
158
+ }
159
+
160
+ /** An object type fits an array only with all of its members; `length` is the first it needs. */
161
+ function objectToList(source: IrObjectType): Assignability {
162
+ return propertiesOf(source).has(ARRAY_LENGTH) ? "undecided" : "rejected";
163
+ }
164
+
165
+ /**
166
+ * The relation of two types of different categories, or null when they are of the same one.
167
+ * Primitive and list targets accept nothing of another category; see `TypeCategory`.
168
+ */
169
+ export function crossCategoryRelation(source: IrType, target: IrType): Assignability | null {
170
+ const from = categoryOf(source);
171
+ const to = categoryOf(target);
172
+ if (from === "opaque" || to === "opaque") {
173
+ return "undecided";
174
+ }
175
+ if (from === to) {
176
+ return null;
177
+ }
178
+ if (to !== "object") {
179
+ return source.kind === "object" && to === "list" ? objectToList(source) : "rejected";
180
+ }
181
+ return from === "primitive" ? primitiveToObject(source) : listToObject(target as IrObjectType);
182
+ }
183
+
184
+ /** The keyword a literal type belongs to. */
185
+ const LITERAL_KEYWORDS = {
186
+ stringLiteral: "string",
187
+ numberLiteral: "number",
188
+ booleanLiteral: "boolean",
189
+ } as const;
190
+
191
+ /**
192
+ * The relation of two primitive types written differently (the same text is assignable). A
193
+ * literal fits its keyword; any other pair is rejected: different keywords, different literals,
194
+ * a keyword for one literal (a union of literals is compared in `type-assignability.ts`), and
195
+ * anything for `never`.
196
+ */
197
+ export function primitiveRelation(source: IrType, target: IrType): Assignability {
198
+ if (target.kind !== "keyword" || source.kind === "keyword") {
199
+ return "rejected";
200
+ }
201
+ const keyword = (LITERAL_KEYWORDS as Partial<Record<IrType["kind"], string>>)[source.kind];
202
+ return provenBy(keyword === target.keyword);
203
+ }
@@ -23,7 +23,7 @@ import { OutputFix } from "../../../domain/output-fixes.js";
23
23
  import { type DeprecatedEntryFilter, documentEntries } from "../document-entries.js";
24
24
  import type { SchemaLocation } from "../schema/schema-context.js";
25
25
  import { transformSchema } from "../schema/schema-transform.js";
26
- import { withSchemaComment } from "../schema-comment.js";
26
+ import { schemaCommentOptionsOf, withSchemaComment } from "../schema-comment.js";
27
27
  import type { TransformContext } from "../transform-context.js";
28
28
  import { headerType } from "./header.js";
29
29
  import { contentEntryType } from "./media-type.js";
@@ -31,7 +31,7 @@ import { parameterType } from "./parameter.js";
31
31
  import { pathItemType } from "./path-item.js";
32
32
  import { requestBodyType } from "./request-body.js";
33
33
  import { responseType } from "./response.js";
34
- import { numberDisplacedAliases, rootTypeAlias } from "./root-type-aliases.js";
34
+ import { rootTypeAlias } from "./root-type-aliases.js";
35
35
 
36
36
  type ComponentTransform = (
37
37
  item: never,
@@ -69,7 +69,7 @@ function componentMember(
69
69
  isOptional: custom?.isOptional ?? false,
70
70
  isReadonly: context.options.immutable,
71
71
  });
72
- return withSchemaComment(property, item);
72
+ return withSchemaComment(property, item, schemaCommentOptionsOf(context.options.fixes));
73
73
  }
74
74
 
75
75
  /**
@@ -112,8 +112,8 @@ function rootTypeName(
112
112
  * Add a root alias. When its exported name is taken (two schemas of one PascalCase name without
113
113
  * the prefix), the later schema takes the name, as in the original. Without the
114
114
  * `root-type-numbering` fix the earlier one then has no alias: that output is valid, so a number
115
- * for it would change it (backlog 30). With the fix the earlier alias is kept aside and numbered
116
- * at the end.
115
+ * for it would change it (backlog 30). With the fix the earlier alias is kept aside and follows
116
+ * the others under the same name; `renameTakenAliases` numbers it once every name is known.
117
117
  */
118
118
  function addRootAlias(request: ComponentsRequest, alias: IrTypeAliasDeclaration): void {
119
119
  const { aliases, displaced } = request;
@@ -184,6 +184,6 @@ export function componentsType(
184
184
  const rootTypes = Object.values(aliases);
185
185
  return {
186
186
  type: irObject(members),
187
- rootTypes: [...rootTypes, ...numberDisplacedAliases(displaced ?? [], rootTypes)],
187
+ rootTypes: [...rootTypes, ...(displaced ?? [])],
188
188
  };
189
189
  }
@@ -0,0 +1,93 @@
1
+ import type { IrDeclaration, IrMember, IrType } from "../../../domain/ir/nodes.js";
2
+
3
+ /**
4
+ * The type a reference of the generated file names, read from the file's declarations: the
5
+ * `--path-params-as-types` overlap check compares two path items through the operations and
6
+ * components they reference (upstream #1752).
7
+ */
8
+
9
+ /** The declared type a reference names, or null when the type is no such reference. */
10
+ export type DeclaredTypeLookup = (type: IrType) => IrType | null;
11
+
12
+ /** Knows no declaration: every reference stays unresolved. */
13
+ export const NO_DECLARED_TYPES: DeclaredTypeLookup = () => null;
14
+
15
+ /** A reference without type arguments and the string keys indexing it: `components["schemas"]`. */
16
+ interface IndexedName {
17
+ readonly name: string;
18
+ readonly keys: readonly string[];
19
+ }
20
+
21
+ function indexedNameOf(type: IrType): IndexedName | null {
22
+ if (type.kind === "reference") {
23
+ return type.typeArguments.length === 0 ? { name: type.name, keys: [] } : null;
24
+ }
25
+ if (type.kind !== "indexedAccess" || type.index.kind !== "stringLiteral") {
26
+ return null;
27
+ }
28
+ const object = indexedNameOf(type.object);
29
+ return object === null ? null : { ...object, keys: [...object.keys, type.index.value] };
30
+ }
31
+
32
+ function memberName(member: IrMember): string | null {
33
+ if (member.kind !== "property") {
34
+ return null;
35
+ }
36
+ return member.name.kind === "string" ? member.name.value : member.name.text;
37
+ }
38
+
39
+ /**
40
+ * The type of the required property `key`. An optional one is `T | undefined` when indexed, which
41
+ * is not the declared type, so it is not followed.
42
+ */
43
+ function propertyType(type: IrType, key: string): IrType | null {
44
+ if (type.kind !== "object") {
45
+ return null;
46
+ }
47
+ const member = type.members.find((candidate) => memberName(candidate) === key);
48
+ return member?.kind === "property" && !member.isOptional ? member.type : null;
49
+ }
50
+
51
+ function declarationType(declaration: IrDeclaration): IrType | null {
52
+ switch (declaration.kind) {
53
+ case "interface":
54
+ return { kind: "object", members: declaration.members };
55
+ case "typeAlias":
56
+ return declaration.type;
57
+ case "enum":
58
+ case "constArray":
59
+ case "rawDeclaration":
60
+ return null;
61
+ }
62
+ }
63
+
64
+ /**
65
+ * A lookup over the declarations. Only a name declared once is read: a second declaration of an
66
+ * interface merges with the first, which this lookup does not model.
67
+ */
68
+ export function declaredTypeLookup(declarations: readonly IrDeclaration[]): DeclaredTypeLookup {
69
+ const byName = new Map<string, IrType | null>();
70
+ for (const declaration of declarations) {
71
+ if ("name" in declaration && declaration.name !== undefined) {
72
+ const isRepeated = byName.has(declaration.name);
73
+ byName.set(declaration.name, isRepeated ? null : declarationType(declaration));
74
+ }
75
+ }
76
+ // References being resolved: a reference that indexes into itself names no type.
77
+ const resolving = new Set<IrType>();
78
+ const lookup: DeclaredTypeLookup = (type) => {
79
+ const indexed = indexedNameOf(type);
80
+ if (indexed === null || indexed.keys.length === 0 || resolving.has(type)) {
81
+ return null;
82
+ }
83
+ resolving.add(type);
84
+ // A key may index a property whose type is a reference again (`paths["/a"]["get"]`).
85
+ const declared = indexed.keys.reduce<IrType | null>((object, key) => {
86
+ const named = object === null || object.kind === "object" ? object : lookup(object);
87
+ return named === null ? null : propertyType(named, key);
88
+ }, byName.get(indexed.name) ?? null);
89
+ resolving.delete(type);
90
+ return declared;
91
+ };
92
+ return lookup;
93
+ }
@@ -1,4 +1,4 @@
1
- import { irKeyword, irRecord } from "../../../domain/ir/constructors.js";
1
+ import { irKeyword, irObject, irRecord } from "../../../domain/ir/constructors.js";
2
2
  import type {
3
3
  IrDeclaration,
4
4
  IrIntersectionType,
@@ -12,9 +12,10 @@ import type { PathsObject } from "../../../domain/openapi/path-item-object.js";
12
12
  import { READ_WRITE_MARKER_TYPES } from "../helper-types.js";
13
13
  import type { TransformContext, TransformOptions } from "../transform-context.js";
14
14
  import { componentsType } from "./components.js";
15
+ import { type DeclaredTypeLookup, declaredTypeLookup } from "./declared-types.js";
15
16
  import { defsRootType } from "./defs-root.js";
16
17
  import { keptDeprecatedComponents } from "./kept-deprecated.js";
17
- import { pathsType } from "./paths.js";
18
+ import { pathMembers, pathsObjectType } from "./paths.js";
18
19
  import { apiPathsEnumDeclaration } from "./paths-enum.js";
19
20
  import { webhooksType } from "./webhooks.js";
20
21
 
@@ -30,6 +31,11 @@ const EMPTY_ROOT_TYPE = irRecord(irKeyword("string"), irKeyword("never"));
30
31
  interface RootResult {
31
32
  readonly type: IrType;
32
33
  readonly aliases: readonly IrTypeAliasDeclaration[];
34
+ /**
35
+ * The root's final type, for a root that reads the types the whole file declares (`paths`, whose
36
+ * overlap check follows references). `type` then only tells whether the root has members.
37
+ */
38
+ readonly withDeclaredTypes?: (lookup: DeclaredTypeLookup) => IrType;
33
39
  }
34
40
 
35
41
  type RootTransform = (
@@ -46,7 +52,14 @@ function keptDeprecated(document: DocumentObject, context: TransformContext): Re
46
52
  }
47
53
 
48
54
  const ROOT_TRANSFORMS: Readonly<Record<RootKey, RootTransform>> = {
49
- paths: (node, context) => ({ type: pathsType(node as PathsObject, context), aliases: [] }),
55
+ paths: (node, context) => {
56
+ const members = pathMembers(node as PathsObject, context);
57
+ return {
58
+ type: irObject(members.map(({ member }) => member)),
59
+ aliases: [],
60
+ withDeclaredTypes: (lookup) => pathsObjectType(members, lookup),
61
+ };
62
+ },
50
63
  webhooks: (node, context) => ({ type: webhooksType(node as PathsObject, context), aliases: [] }),
51
64
  components: (node, context, document) => {
52
65
  const kept = keptDeprecated(document, context);
@@ -84,6 +97,15 @@ function rootDeclaration(
84
97
  : { kind: "interface", name, isExported: true, members: type.members, comments: [] };
85
98
  }
86
99
 
100
+ /**
101
+ * The declarations of one root: its type, then its aliases. `finish` gives the root's declaration
102
+ * again from the types the whole file declares, for a root with `withDeclaredTypes`.
103
+ */
104
+ interface RootDeclarations {
105
+ readonly declarations: readonly IrDeclaration[];
106
+ readonly finish?: (lookup: DeclaredTypeLookup) => IrDeclaration;
107
+ }
108
+
87
109
  /**
88
110
  * The declarations of one root. A root that is missing, or whose type has no members, is
89
111
  * `Record<string, never>`. Only an object type (or the `paths` intersection) counts as having
@@ -93,21 +115,51 @@ function rootDeclarations(
93
115
  root: RootKey,
94
116
  document: DocumentObject,
95
117
  context: TransformContext,
96
- ): IrDeclaration[] {
118
+ ): RootDeclarations {
97
119
  const node: unknown = document[root];
98
120
  if (!node || typeof node !== "object") {
99
121
  context.diagnostics.debug(`${root} done (skipped)`, 0);
100
- return [emptyAlias(root)];
122
+ return { declarations: [emptyAlias(root)] };
101
123
  }
102
124
  const startedAt = context.diagnostics.now();
103
- const { type, aliases } = ROOT_TRANSFORMS[root](node, context, document);
125
+ const { type, aliases, withDeclaredTypes } = ROOT_TRANSFORMS[root](node, context, document);
104
126
  if (!isRootBody(type)) {
105
127
  context.diagnostics.debug(`${root} done (skipped)`, 0);
106
- return [emptyAlias(root), ...aliases];
128
+ return { declarations: [emptyAlias(root), ...aliases] };
107
129
  }
108
130
  const declaration = rootDeclaration(root, type, context.options);
109
131
  context.diagnostics.debug(`${root} done`, context.diagnostics.now() - startedAt);
110
- return [declaration, ...aliases];
132
+ const finish =
133
+ withDeclaredTypes &&
134
+ ((lookup: DeclaredTypeLookup) => {
135
+ const finished = withDeclaredTypes(lookup);
136
+ return isRootBody(finished) ? rootDeclaration(root, finished, context.options) : declaration;
137
+ });
138
+ return { declarations: [declaration, ...aliases], ...(finish ? { finish } : {}) };
139
+ }
140
+
141
+ /**
142
+ * Every root's declarations in output order. A root with `finish` gets its declaration from the
143
+ * types of the other roots and of the footer (the `operations` interface), which exist only once
144
+ * every root is transformed.
145
+ */
146
+ function rootsDeclarations(
147
+ document: DocumentObject,
148
+ context: TransformContext,
149
+ footer: () => readonly IrDeclaration[],
150
+ ): IrDeclaration[] {
151
+ const roots = ROOT_KEYS.map((root) => rootDeclarations(root, document, context));
152
+ const transformed = roots.flatMap(({ declarations }) => declarations);
153
+ if (roots.every(({ finish }) => finish === undefined)) {
154
+ return transformed;
155
+ }
156
+ const lookup = declaredTypeLookup([...transformed, ...footer()]);
157
+ return roots.flatMap(({ declarations, finish }) => {
158
+ const [declaration, ...aliases] = declarations;
159
+ return finish === undefined || declaration === undefined
160
+ ? declarations
161
+ : [finish(lookup), ...aliases];
162
+ });
111
163
  }
112
164
 
113
165
  /**
@@ -118,11 +170,19 @@ function withoutBlankLinesAround(text: string): string {
118
170
  return text.replace(/^(?:[ \t]*\r?\n)+/, "").trimEnd();
119
171
  }
120
172
 
173
+ /** No footer declarations: the roots' references to `operations` stay unresolved. */
174
+ const NO_FOOTER = (): readonly IrDeclaration[] => [];
175
+
121
176
  /**
122
177
  * The document's declarations before the footer: helper types, injected code, then one root type
123
- * per root key with the aliases that follow it.
178
+ * per root key with the aliases that follow it. `footer` reads the footer's declarations once the
179
+ * roots are transformed.
124
180
  */
125
- export function documentBody(document: DocumentObject, context: TransformContext): IrDeclaration[] {
181
+ export function documentBody(
182
+ document: DocumentObject,
183
+ context: TransformContext,
184
+ footer: () => readonly IrDeclaration[] = NO_FOOTER,
185
+ ): IrDeclaration[] {
126
186
  const declarations: IrDeclaration[] = [];
127
187
  if (context.options.readWriteMarkers) {
128
188
  declarations.push({ kind: "rawDeclaration", text: READ_WRITE_MARKER_TYPES });
@@ -131,9 +191,7 @@ export function documentBody(document: DocumentObject, context: TransformContext
131
191
  if (inject !== "") {
132
192
  declarations.push({ kind: "rawDeclaration", text: inject });
133
193
  }
134
- for (const root of ROOT_KEYS) {
135
- declarations.push(...rootDeclarations(root, document, context));
136
- }
194
+ declarations.push(...rootsDeclarations(document, context, footer));
137
195
  return declarations;
138
196
  }
139
197
 
@@ -0,0 +1,91 @@
1
+ import type { ParameterObject } from "../../../domain/openapi/parameter-object.js";
2
+ import type { Schema, SchemaObject } from "../../../domain/openapi/schema-object.js";
3
+ import type { RefResolver } from "../schema/schema-context.js";
4
+
5
+ /**
6
+ * Exploded object query parameters (OpenAPI 3.2 §4.8.12.4, upstream #2361). A query parameter has
7
+ * `style: form` and, with it, `explode: true` by default. An object value is then sent as its own
8
+ * `key=value` pairs, `?page=1&limit=10`: the parameter's name is not in the URL. So with the
9
+ * `exploded-query-objects` fix, the object's properties are keys of `query`, not one key named
10
+ * after the parameter.
11
+ */
12
+
13
+ const FORM_STYLE = "form";
14
+
15
+ /** Whether a `$ref` chain has been followed already, so a cycle ends. */
16
+ type SeenRefs = ReadonlySet<string>;
17
+
18
+ function resolvedSchema(schema: Schema, refs: RefResolver, seen: SeenRefs): SchemaObject | null {
19
+ if (typeof schema !== "object" || schema === null) {
20
+ return null;
21
+ }
22
+ if (!("$ref" in schema)) {
23
+ return schema;
24
+ }
25
+ if (seen.has(schema.$ref)) {
26
+ return null;
27
+ }
28
+ const target = refs.resolve(schema.$ref) as Schema | undefined;
29
+ return target === undefined
30
+ ? null
31
+ : resolvedSchema(target, refs, new Set([...seen, schema.$ref]));
32
+ }
33
+
34
+ /** The types of a `type` keyword other than `null`; empty when it has none. */
35
+ function nonNullTypes(schema: SchemaObject): readonly unknown[] {
36
+ const types = Array.isArray(schema.type) ? schema.type : [schema.type];
37
+ return types.filter((type) => type !== undefined && type !== null && type !== "null");
38
+ }
39
+
40
+ /** Whether every member of a composition is an object schema; false for an empty list. */
41
+ function isObjectComposition(
42
+ members: readonly Schema[] | undefined,
43
+ refs: RefResolver,
44
+ seen: SeenRefs,
45
+ ): boolean {
46
+ return (
47
+ members !== undefined &&
48
+ members.length > 0 &&
49
+ members.every((member) => isObjectSchemaWith(member, refs, seen))
50
+ );
51
+ }
52
+
53
+ function isObjectSchemaWith(schema: Schema, refs: RefResolver, seen: SeenRefs): boolean {
54
+ const resolved = resolvedSchema(schema, refs, seen);
55
+ if (resolved === null) {
56
+ return false;
57
+ }
58
+ const types = nonNullTypes(resolved);
59
+ if (types.length > 0) {
60
+ return types.every((type) => type === "object");
61
+ }
62
+ return (
63
+ resolved.properties !== undefined ||
64
+ resolved.additionalProperties !== undefined ||
65
+ resolved.patternProperties !== undefined ||
66
+ isObjectComposition(resolved.allOf, refs, seen) ||
67
+ isObjectComposition(resolved.oneOf, refs, seen) ||
68
+ isObjectComposition(resolved.anyOf, refs, seen)
69
+ );
70
+ }
71
+
72
+ /**
73
+ * Whether the schema describes only objects: `type: object` (with `null` or not), or no `type`
74
+ * and object keywords, or a composition of such schemas. A `$ref` is followed.
75
+ */
76
+ export function isObjectSchema(schema: Schema, refs: RefResolver): boolean {
77
+ return isObjectSchemaWith(schema, refs, new Set());
78
+ }
79
+
80
+ /** Whether an `in: query` parameter sends its object value as the object's own keys. */
81
+ export function isExplodedQueryObject(parameter: ParameterObject, refs: RefResolver): boolean {
82
+ const style = parameter.style ?? FORM_STYLE;
83
+ const explode = parameter.explode ?? style === FORM_STYLE;
84
+ return (
85
+ parameter.in === "query" &&
86
+ style === FORM_STYLE &&
87
+ explode &&
88
+ parameter.schema !== undefined &&
89
+ isObjectSchema(parameter.schema, refs)
90
+ );
91
+ }