openapi-typesafe 1.0.0-rc.4 → 1.0.0-rc.5

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 (320) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/dist/adapters/node-file-system/node-file-system.cjs +7 -0
  3. package/dist/adapters/node-file-system/node-file-system.cjs.map +1 -1
  4. package/dist/adapters/node-file-system/node-file-system.mjs +7 -0
  5. package/dist/adapters/node-file-system/node-file-system.mjs.map +1 -1
  6. package/dist/adapters/pipe-safe-writer/pipe-safe-writer.cjs +19 -0
  7. package/dist/adapters/pipe-safe-writer/pipe-safe-writer.cjs.map +1 -0
  8. package/dist/adapters/pipe-safe-writer/pipe-safe-writer.mjs +17 -0
  9. package/dist/adapters/pipe-safe-writer/pipe-safe-writer.mjs.map +1 -0
  10. package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs +5 -32
  11. package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs.map +1 -1
  12. package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs +6 -33
  13. package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs.map +1 -1
  14. package/dist/adapters/redocly-loader/component-schemas.cjs +17 -0
  15. package/dist/adapters/redocly-loader/component-schemas.cjs.map +1 -0
  16. package/dist/adapters/redocly-loader/component-schemas.mjs +14 -0
  17. package/dist/adapters/redocly-loader/component-schemas.mjs.map +1 -0
  18. package/dist/adapters/redocly-loader/mapping-schema-names.cjs +22 -0
  19. package/dist/adapters/redocly-loader/mapping-schema-names.cjs.map +1 -0
  20. package/dist/adapters/redocly-loader/mapping-schema-names.mjs +20 -0
  21. package/dist/adapters/redocly-loader/mapping-schema-names.mjs.map +1 -0
  22. package/dist/adapters/redocly-loader/query-named-components.cjs +137 -0
  23. package/dist/adapters/redocly-loader/query-named-components.cjs.map +1 -0
  24. package/dist/adapters/redocly-loader/query-named-components.mjs +134 -0
  25. package/dist/adapters/redocly-loader/query-named-components.mjs.map +1 -0
  26. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +26 -14
  27. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  28. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +26 -14
  29. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  30. package/dist/adapters/redocly-loader/ref-access.cjs +16 -3
  31. package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
  32. package/dist/adapters/redocly-loader/ref-access.mjs +16 -3
  33. package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
  34. package/dist/adapters/redocly-loader/ref-holders.cjs +52 -10
  35. package/dist/adapters/redocly-loader/ref-holders.cjs.map +1 -1
  36. package/dist/adapters/redocly-loader/ref-holders.mjs +52 -11
  37. package/dist/adapters/redocly-loader/ref-holders.mjs.map +1 -1
  38. package/dist/adapters/redocly-loader/remote-fetch.cjs +5 -3
  39. package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
  40. package/dist/adapters/redocly-loader/remote-fetch.mjs +5 -3
  41. package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
  42. package/dist/adapters/redocly-loader/remote-node-limit.cjs +17 -3
  43. package/dist/adapters/redocly-loader/remote-node-limit.cjs.map +1 -1
  44. package/dist/adapters/redocly-loader/remote-node-limit.mjs +17 -3
  45. package/dist/adapters/redocly-loader/remote-node-limit.mjs.map +1 -1
  46. package/dist/adapters/redocly-loader/schema-source.cjs +7 -2
  47. package/dist/adapters/redocly-loader/schema-source.cjs.map +1 -1
  48. package/dist/adapters/redocly-loader/schema-source.mjs +8 -3
  49. package/dist/adapters/redocly-loader/schema-source.mjs.map +1 -1
  50. package/dist/adapters/terminal-logger/terminal-logger.cjs +7 -3
  51. package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
  52. package/dist/adapters/terminal-logger/terminal-logger.mjs +7 -3
  53. package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
  54. package/dist/api/document-refs.cjs +16 -1
  55. package/dist/api/document-refs.cjs.map +1 -1
  56. package/dist/api/document-refs.mjs +16 -1
  57. package/dist/api/document-refs.mjs.map +1 -1
  58. package/dist/api/document-run.cjs +4 -10
  59. package/dist/api/document-run.cjs.map +1 -1
  60. package/dist/api/document-run.mjs +4 -10
  61. package/dist/api/document-run.mjs.map +1 -1
  62. package/dist/api/openapi-ts.cjs +1 -2
  63. package/dist/api/openapi-ts.cjs.map +1 -1
  64. package/dist/api/openapi-ts.mjs +2 -3
  65. package/dist/api/openapi-ts.mjs.map +1 -1
  66. package/dist/api/ref-limit-options.cjs +63 -0
  67. package/dist/api/ref-limit-options.cjs.map +1 -0
  68. package/dist/api/ref-limit-options.mjs +57 -0
  69. package/dist/api/ref-limit-options.mjs.map +1 -0
  70. package/dist/api/transform-schema.cjs +12 -5
  71. package/dist/api/transform-schema.cjs.map +1 -1
  72. package/dist/api/transform-schema.mjs +12 -5
  73. package/dist/api/transform-schema.mjs.map +1 -1
  74. package/dist/application/transform/declared-value-types.cjs +36 -0
  75. package/dist/application/transform/declared-value-types.cjs.map +1 -0
  76. package/dist/application/transform/declared-value-types.mjs +34 -0
  77. package/dist/application/transform/declared-value-types.mjs.map +1 -0
  78. package/dist/application/transform/document/document.cjs +3 -1
  79. package/dist/application/transform/document/document.cjs.map +1 -1
  80. package/dist/application/transform/document/document.mjs +3 -1
  81. package/dist/application/transform/document/document.mjs.map +1 -1
  82. package/dist/application/transform/document/exploded-query-values.cjs +63 -0
  83. package/dist/application/transform/document/exploded-query-values.cjs.map +1 -0
  84. package/dist/application/transform/document/exploded-query-values.mjs +59 -0
  85. package/dist/application/transform/document/exploded-query-values.mjs.map +1 -0
  86. package/dist/application/transform/document/header.cjs +11 -3
  87. package/dist/application/transform/document/header.cjs.map +1 -1
  88. package/dist/application/transform/document/header.mjs +10 -2
  89. package/dist/application/transform/document/header.mjs.map +1 -1
  90. package/dist/application/transform/document/parameter.cjs +1 -1
  91. package/dist/application/transform/document/parameter.cjs.map +1 -1
  92. package/dist/application/transform/document/parameter.mjs +1 -1
  93. package/dist/application/transform/document/parameter.mjs.map +1 -1
  94. package/dist/application/transform/document/parameters.cjs +23 -5
  95. package/dist/application/transform/document/parameters.cjs.map +1 -1
  96. package/dist/application/transform/document/parameters.mjs +23 -5
  97. package/dist/application/transform/document/parameters.mjs.map +1 -1
  98. package/dist/application/transform/document/path-item.cjs +4 -3
  99. package/dist/application/transform/document/path-item.cjs.map +1 -1
  100. package/dist/application/transform/document/path-item.mjs +4 -4
  101. package/dist/application/transform/document/path-item.mjs.map +1 -1
  102. package/dist/application/transform/document/path-template-key.cjs +84 -0
  103. package/dist/application/transform/document/path-template-key.cjs.map +1 -0
  104. package/dist/application/transform/document/path-template-key.mjs +79 -0
  105. package/dist/application/transform/document/path-template-key.mjs.map +1 -0
  106. package/dist/application/transform/document/path-template-refs.cjs +70 -0
  107. package/dist/application/transform/document/path-template-refs.cjs.map +1 -0
  108. package/dist/application/transform/document/path-template-refs.mjs +67 -0
  109. package/dist/application/transform/document/path-template-refs.mjs.map +1 -0
  110. package/dist/application/transform/document/paths-enum.cjs +7 -6
  111. package/dist/application/transform/document/paths-enum.cjs.map +1 -1
  112. package/dist/application/transform/document/paths-enum.mjs +7 -6
  113. package/dist/application/transform/document/paths-enum.mjs.map +1 -1
  114. package/dist/application/transform/document/paths.cjs +38 -66
  115. package/dist/application/transform/document/paths.cjs.map +1 -1
  116. package/dist/application/transform/document/paths.mjs +36 -66
  117. package/dist/application/transform/document/paths.mjs.map +1 -1
  118. package/dist/application/transform/document/webhooks.cjs +10 -3
  119. package/dist/application/transform/document/webhooks.cjs.map +1 -1
  120. package/dist/application/transform/document/webhooks.mjs +9 -2
  121. package/dist/application/transform/document/webhooks.mjs.map +1 -1
  122. package/dist/application/transform/footer/document-footer.cjs +6 -3
  123. package/dist/application/transform/footer/document-footer.cjs.map +1 -1
  124. package/dist/application/transform/footer/document-footer.mjs +6 -3
  125. package/dist/application/transform/footer/document-footer.mjs.map +1 -1
  126. package/dist/application/transform/helper-types.cjs +3 -3
  127. package/dist/application/transform/helper-types.cjs.map +1 -1
  128. package/dist/application/transform/helper-types.mjs +3 -3
  129. package/dist/application/transform/helper-types.mjs.map +1 -1
  130. package/dist/application/transform/ref-pointer/generated-location.cjs +4 -4
  131. package/dist/application/transform/ref-pointer/generated-location.cjs.map +1 -1
  132. package/dist/application/transform/ref-pointer/generated-location.mjs +1 -1
  133. package/dist/application/transform/ref-pointer/generated-location.mjs.map +1 -1
  134. package/dist/application/transform/ref-pointer/generated-shape.cjs +47 -66
  135. package/dist/application/transform/ref-pointer/generated-shape.cjs.map +1 -1
  136. package/dist/application/transform/ref-pointer/generated-shape.mjs +36 -50
  137. package/dist/application/transform/ref-pointer/generated-shape.mjs.map +1 -1
  138. package/dist/application/transform/ref-pointer/inline-cycles.cjs +2 -2
  139. package/dist/application/transform/ref-pointer/inline-cycles.cjs.map +1 -1
  140. package/dist/application/transform/ref-pointer/inline-cycles.mjs +1 -1
  141. package/dist/application/transform/ref-pointer/inline-cycles.mjs.map +1 -1
  142. package/dist/application/transform/ref-pointer/located-node.cjs +23 -0
  143. package/dist/application/transform/ref-pointer/located-node.cjs.map +1 -0
  144. package/dist/application/transform/ref-pointer/located-node.mjs +18 -0
  145. package/dist/application/transform/ref-pointer/located-node.mjs.map +1 -0
  146. package/dist/application/transform/ref-pointer/original-index.cjs +68 -13
  147. package/dist/application/transform/ref-pointer/original-index.cjs.map +1 -1
  148. package/dist/application/transform/ref-pointer/original-index.mjs +70 -15
  149. package/dist/application/transform/ref-pointer/original-index.mjs.map +1 -1
  150. package/dist/application/transform/ref-pointer/pointer-type.cjs +2 -2
  151. package/dist/application/transform/ref-pointer/pointer-type.cjs.map +1 -1
  152. package/dist/application/transform/ref-pointer/pointer-type.mjs +1 -1
  153. package/dist/application/transform/ref-pointer/pointer-type.mjs.map +1 -1
  154. package/dist/application/transform/ref-pointer/pointer-walk.cjs +54 -13
  155. package/dist/application/transform/ref-pointer/pointer-walk.cjs.map +1 -1
  156. package/dist/application/transform/ref-pointer/pointer-walk.mjs +46 -7
  157. package/dist/application/transform/ref-pointer/pointer-walk.mjs.map +1 -1
  158. package/dist/application/transform/ref-pointer/property-shape.cjs +52 -0
  159. package/dist/application/transform/ref-pointer/property-shape.cjs.map +1 -0
  160. package/dist/application/transform/ref-pointer/property-shape.mjs +45 -0
  161. package/dist/application/transform/ref-pointer/property-shape.mjs.map +1 -0
  162. package/dist/application/transform/ref-pointer/typed-walk.cjs +79 -21
  163. package/dist/application/transform/ref-pointer/typed-walk.cjs.map +1 -1
  164. package/dist/application/transform/ref-pointer/typed-walk.mjs +76 -18
  165. package/dist/application/transform/ref-pointer/typed-walk.mjs.map +1 -1
  166. package/dist/application/transform/schema/access-flags.cjs.map +1 -1
  167. package/dist/application/transform/schema/access-flags.mjs.map +1 -1
  168. package/dist/application/transform/schema/index-signature-type.cjs +21 -0
  169. package/dist/application/transform/schema/index-signature-type.cjs.map +1 -0
  170. package/dist/application/transform/schema/index-signature-type.mjs +19 -0
  171. package/dist/application/transform/schema/index-signature-type.mjs.map +1 -0
  172. package/dist/application/transform/schema/input-location.cjs +23 -0
  173. package/dist/application/transform/schema/input-location.cjs.map +1 -0
  174. package/dist/application/transform/schema/input-location.mjs +21 -0
  175. package/dist/application/transform/schema/input-location.mjs.map +1 -0
  176. package/dist/application/transform/schema/object-type.cjs +15 -5
  177. package/dist/application/transform/schema/object-type.cjs.map +1 -1
  178. package/dist/application/transform/schema/object-type.mjs +15 -5
  179. package/dist/application/transform/schema/object-type.mjs.map +1 -1
  180. package/dist/application/transform/schema/schema-transform.cjs +2 -1
  181. package/dist/application/transform/schema/schema-transform.cjs.map +1 -1
  182. package/dist/application/transform/schema/schema-transform.mjs +2 -1
  183. package/dist/application/transform/schema/schema-transform.mjs.map +1 -1
  184. package/dist/application/transform/schema/unevaluated-properties.cjs +2 -30
  185. package/dist/application/transform/schema/unevaluated-properties.cjs.map +1 -1
  186. package/dist/application/transform/schema/unevaluated-properties.mjs +3 -31
  187. package/dist/application/transform/schema/unevaluated-properties.mjs.map +1 -1
  188. package/dist/cli/api-logger.cjs +13 -0
  189. package/dist/cli/api-logger.cjs.map +1 -0
  190. package/dist/cli/api-logger.mjs +11 -0
  191. package/dist/cli/api-logger.mjs.map +1 -0
  192. package/dist/cli/api-settings.cjs +7 -15
  193. package/dist/cli/api-settings.cjs.map +1 -1
  194. package/dist/cli/api-settings.mjs +8 -16
  195. package/dist/cli/api-settings.mjs.map +1 -1
  196. package/dist/cli/cli-arguments.cjs +51 -11
  197. package/dist/cli/cli-arguments.cjs.map +1 -1
  198. package/dist/cli/cli-arguments.mjs +51 -11
  199. package/dist/cli/cli-arguments.mjs.map +1 -1
  200. package/dist/cli/cli-errors.cjs +17 -0
  201. package/dist/cli/cli-errors.cjs.map +1 -1
  202. package/dist/cli/cli-errors.mjs +17 -1
  203. package/dist/cli/cli-errors.mjs.map +1 -1
  204. package/dist/cli/cli-output.cjs +6 -4
  205. package/dist/cli/cli-output.cjs.map +1 -1
  206. package/dist/cli/cli-output.mjs +7 -5
  207. package/dist/cli/cli-output.mjs.map +1 -1
  208. package/dist/cli/main.cjs +10 -4
  209. package/dist/cli/main.cjs.map +1 -1
  210. package/dist/cli/main.mjs +10 -4
  211. package/dist/cli/main.mjs.map +1 -1
  212. package/dist/cli/redocly-apis.cjs +20 -9
  213. package/dist/cli/redocly-apis.cjs.map +1 -1
  214. package/dist/cli/redocly-apis.mjs +22 -11
  215. package/dist/cli/redocly-apis.mjs.map +1 -1
  216. package/dist/cli/ref-limits.cjs +15 -1
  217. package/dist/cli/ref-limits.cjs.map +1 -1
  218. package/dist/cli/ref-limits.mjs +15 -2
  219. package/dist/cli/ref-limits.mjs.map +1 -1
  220. package/dist/cli/run-cli.cjs +24 -7
  221. package/dist/cli/run-cli.cjs.map +1 -1
  222. package/dist/cli/run-cli.mjs +25 -8
  223. package/dist/cli/run-cli.mjs.map +1 -1
  224. package/dist/domain/cause-secrets.cjs +31 -16
  225. package/dist/domain/cause-secrets.cjs.map +1 -1
  226. package/dist/domain/cause-secrets.mjs +31 -16
  227. package/dist/domain/cause-secrets.mjs.map +1 -1
  228. package/dist/domain/errors.cjs +2 -0
  229. package/dist/domain/errors.cjs.map +1 -1
  230. package/dist/domain/errors.mjs +2 -0
  231. package/dist/domain/errors.mjs.map +1 -1
  232. package/dist/domain/location-text.cjs +9 -0
  233. package/dist/domain/location-text.cjs.map +1 -0
  234. package/dist/domain/location-text.mjs +7 -0
  235. package/dist/domain/location-text.mjs.map +1 -0
  236. package/dist/domain/openapi/document-places.cjs +55 -0
  237. package/dist/domain/openapi/document-places.cjs.map +1 -0
  238. package/dist/domain/openapi/document-places.mjs +53 -0
  239. package/dist/domain/openapi/document-places.mjs.map +1 -0
  240. package/dist/domain/output-fixes.cjs +8 -2
  241. package/dist/domain/output-fixes.cjs.map +1 -1
  242. package/dist/domain/output-fixes.mjs +8 -2
  243. package/dist/domain/output-fixes.mjs.map +1 -1
  244. package/dist/domain/terminal-text.cjs +31 -0
  245. package/dist/domain/terminal-text.cjs.map +1 -0
  246. package/dist/domain/terminal-text.mjs +29 -0
  247. package/dist/domain/terminal-text.mjs.map +1 -0
  248. package/dist/domain/warnings.cjs +1 -0
  249. package/dist/domain/warnings.cjs.map +1 -1
  250. package/dist/domain/warnings.mjs +1 -0
  251. package/dist/domain/warnings.mjs.map +1 -1
  252. package/dist/index.d.cts +26 -18
  253. package/dist/index.d.mts +26 -18
  254. package/dist/index.d.ts +26 -18
  255. package/package.json +1 -1
  256. package/src/adapters/node-file-system/node-file-system.ts +14 -1
  257. package/src/adapters/pipe-safe-writer/pipe-safe-writer.ts +29 -0
  258. package/src/adapters/redocly-loader/bundled-mapping-copies.ts +7 -42
  259. package/src/adapters/redocly-loader/component-schemas.ts +19 -0
  260. package/src/adapters/redocly-loader/mapping-schema-names.ts +25 -0
  261. package/src/adapters/redocly-loader/query-named-components.ts +201 -0
  262. package/src/adapters/redocly-loader/redocly-schema-loader.ts +46 -14
  263. package/src/adapters/redocly-loader/ref-access.ts +16 -3
  264. package/src/adapters/redocly-loader/ref-holders.ts +75 -14
  265. package/src/adapters/redocly-loader/remote-fetch.ts +11 -2
  266. package/src/adapters/redocly-loader/remote-node-limit.ts +44 -4
  267. package/src/adapters/redocly-loader/schema-source.ts +15 -3
  268. package/src/adapters/terminal-logger/terminal-logger.ts +10 -4
  269. package/src/api/document-refs.ts +29 -1
  270. package/src/api/document-run.ts +6 -16
  271. package/src/api/openapi-ts.ts +6 -3
  272. package/src/api/ref-limit-options.ts +76 -0
  273. package/src/api/transform-schema.ts +20 -5
  274. package/src/application/ports/file-system.ts +8 -0
  275. package/src/application/transform/declared-value-types.ts +42 -0
  276. package/src/application/transform/document/document.ts +4 -2
  277. package/src/application/transform/document/exploded-query-values.ts +88 -0
  278. package/src/application/transform/document/header.ts +25 -5
  279. package/src/application/transform/document/parameter.ts +4 -4
  280. package/src/application/transform/document/parameters.ts +45 -10
  281. package/src/application/transform/document/path-item.ts +3 -3
  282. package/src/application/transform/document/path-template-key.ts +152 -0
  283. package/src/application/transform/document/path-template-refs.ts +106 -0
  284. package/src/application/transform/document/paths-enum.ts +23 -9
  285. package/src/application/transform/document/paths.ts +53 -123
  286. package/src/application/transform/document/webhooks.ts +18 -5
  287. package/src/application/transform/footer/document-footer.ts +18 -8
  288. package/src/application/transform/helper-types.ts +4 -4
  289. package/src/application/transform/ref-pointer/generated-location.ts +1 -1
  290. package/src/application/transform/ref-pointer/generated-shape.ts +67 -78
  291. package/src/application/transform/ref-pointer/inline-cycles.ts +1 -1
  292. package/src/application/transform/ref-pointer/located-node.ts +38 -0
  293. package/src/application/transform/ref-pointer/original-index.ts +100 -17
  294. package/src/application/transform/ref-pointer/pointer-type.ts +1 -1
  295. package/src/application/transform/ref-pointer/pointer-walk.ts +82 -8
  296. package/src/application/transform/ref-pointer/property-shape.ts +97 -0
  297. package/src/application/transform/ref-pointer/typed-walk.ts +117 -25
  298. package/src/application/transform/schema/access-flags.ts +5 -2
  299. package/src/application/transform/schema/index-signature-type.ts +31 -0
  300. package/src/application/transform/schema/input-location.ts +35 -0
  301. package/src/application/transform/schema/object-type.ts +18 -8
  302. package/src/application/transform/schema/schema-transform.ts +2 -1
  303. package/src/application/transform/schema/unevaluated-properties.ts +4 -44
  304. package/src/cli/api-logger.ts +17 -0
  305. package/src/cli/api-settings.ts +12 -20
  306. package/src/cli/cli-arguments.ts +90 -14
  307. package/src/cli/cli-dependencies.ts +7 -2
  308. package/src/cli/cli-errors.ts +22 -0
  309. package/src/cli/cli-output.ts +11 -10
  310. package/src/cli/main.ts +10 -4
  311. package/src/cli/redocly-apis.ts +37 -17
  312. package/src/cli/ref-limits.ts +21 -2
  313. package/src/cli/run-cli.ts +39 -7
  314. package/src/domain/cause-secrets.ts +59 -25
  315. package/src/domain/errors.ts +2 -0
  316. package/src/domain/location-text.ts +12 -0
  317. package/src/domain/openapi/document-places.ts +78 -0
  318. package/src/domain/output-fixes.ts +9 -2
  319. package/src/domain/terminal-text.ts +44 -0
  320. package/src/domain/warnings.ts +1 -0
@@ -0,0 +1,97 @@
1
+ import type { Schema } from "../../../domain/openapi/schema-object.js";
2
+ import { propertyAccessFlags } from "../schema/access-flags.js";
3
+ import { isInputLocation } from "../schema/input-location.js";
4
+ import type { RefResolver, SchemaOptions } from "../schema/schema-context.js";
5
+ import {
6
+ followLocated,
7
+ followRefs,
8
+ isJsonObject,
9
+ type JsonObject,
10
+ type LocatedNode,
11
+ } from "./located-node.js";
12
+
13
+ /**
14
+ * What the generated type of an object schema makes of one of its properties: optional, left
15
+ * out, wrapped in a read or write marker, or a value that may be `undefined` or `null`.
16
+ */
17
+
18
+ /** `nullable: true` or a `type` list with `null`: the generated type includes `null`. */
19
+ export function isNullableSchema(node: unknown): boolean {
20
+ if (!isJsonObject(node)) {
21
+ return false;
22
+ }
23
+ return node.nullable === true || (Array.isArray(node.type) && node.type.includes("null"));
24
+ }
25
+
26
+ /**
27
+ * Whether the generated type has the property as optional, as `objectType` decides: a `default`
28
+ * makes a property required except in an input location, where the client may leave it out.
29
+ */
30
+ export function isOptionalProperty(
31
+ owner: LocatedNode & { node: JsonObject },
32
+ key: string,
33
+ options: SchemaOptions,
34
+ ): boolean {
35
+ const { node } = owner;
36
+ const required = Array.isArray(node.required) ? node.required : undefined;
37
+ const property = (node.properties as JsonObject)[key];
38
+ const hasDefault = isJsonObject(property) && property.default !== undefined;
39
+ const isDefaultRequired =
40
+ hasDefault && options.defaultNonNullable && !isInputLocation(owner.location, options.fixes);
41
+ const isRequired =
42
+ required?.includes(key) === true ||
43
+ (required === undefined && options.propertiesRequiredByDefault) ||
44
+ isDefaultRequired;
45
+ return !isRequired;
46
+ }
47
+
48
+ /**
49
+ * Whether `--exclude-deprecated` leaves the property out: its object, or the one its `$ref`
50
+ * names, is deprecated (`objectType`). The key is then not in the generated type at all.
51
+ */
52
+ export function isExcludedProperty(
53
+ property: unknown,
54
+ options: SchemaOptions,
55
+ refs: RefResolver,
56
+ ): boolean {
57
+ const resolved = followRefs(property, refs);
58
+ return options.excludeDeprecated && isJsonObject(resolved) && Boolean(resolved.deprecated);
59
+ }
60
+
61
+ /**
62
+ * Whether `--read-write-markers` wraps the type of a property or `$defs` member: one that is only
63
+ * `readOnly` or only `writeOnly` is `$Read<T>` or `$Write<T>` (object-type.ts).
64
+ */
65
+ export function isMarkedProperty(value: unknown, options: SchemaOptions, refs: RefResolver) {
66
+ if (!options.readWriteMarkers || !isJsonObject(value)) {
67
+ return false;
68
+ }
69
+ const flags = propertyAccessFlags(value as Schema, { options, refs });
70
+ return flags.isReadOnly !== flags.isWriteOnly;
71
+ }
72
+
73
+ /** Whether a value is there: `present`, `absent` (may be `undefined` or `null`) or `unknown`. */
74
+ export type Presence = "present" | "absent" | "unknown";
75
+
76
+ /** The presence of a value of `schema` itself: `absent` when its type includes `null`. */
77
+ export function schemaPresence(schema: unknown, refs: RefResolver): Presence {
78
+ return isNullableSchema(followRefs(schema, refs)) ? "absent" : "present";
79
+ }
80
+
81
+ /** The presence of property `key` of `schema`: optional or nullable is `absent`. */
82
+ export function propertyPresence(
83
+ schema: LocatedNode,
84
+ key: string,
85
+ options: SchemaOptions,
86
+ refs: RefResolver,
87
+ ): Presence {
88
+ const owner = followLocated(schema, refs);
89
+ const { node } = owner;
90
+ const properties = isJsonObject(node) && isJsonObject(node.properties) ? node.properties : {};
91
+ if (!isJsonObject(node) || !Object.hasOwn(properties, key)) {
92
+ return "unknown";
93
+ }
94
+ return isOptionalProperty({ ...owner, node }, key, options)
95
+ ? "absent"
96
+ : schemaPresence(properties[key], refs);
97
+ }
@@ -2,25 +2,34 @@ import { irIndexedAccess, irKeyword, irReference } from "../../../domain/ir/cons
2
2
  import type { IrKeyword, IrType } from "../../../domain/ir/nodes.js";
3
3
  import { indexBy, indexKeyword, rootType } from "../ref-type.js";
4
4
  import type { RefResolver, SchemaOptions } from "../schema/schema-context.js";
5
+ import { isArrayShape, isObjectShape } from "./generated-shape.js";
5
6
  import {
7
+ childLocation,
8
+ followLocated,
6
9
  followRefs,
7
- isArrayShape,
8
10
  isJsonObject,
9
- isObjectShape,
10
- type Presence,
11
- propertyPresence,
12
- schemaPresence,
13
- } from "./generated-shape.js";
11
+ type LocatedNode,
12
+ } from "./located-node.js";
14
13
  import {
14
+ type ContentForm,
15
+ headerParameterContentForm,
16
+ isExcludedOperation,
15
17
  isOptionalHeader,
16
18
  isOptionalRequestBody,
17
19
  isParameterList,
18
20
  isTypedAsSchema,
19
- leadingNode,
21
+ leadingLocated,
20
22
  leadingSegmentsOf,
21
23
  type RefPointer,
22
24
  startsInSchema,
23
25
  } from "./pointer-walk.js";
26
+ import {
27
+ isExcludedProperty,
28
+ isMarkedProperty,
29
+ type Presence,
30
+ propertyPresence,
31
+ schemaPresence,
32
+ } from "./property-shape.js";
24
33
 
25
34
  /**
26
35
  * The type of a `$ref` pointer as the generated types have it: the `schema` of a media type,
@@ -36,9 +45,18 @@ export interface TypedWalk {
36
45
  readonly type: IrType;
37
46
  /** The document node the type stands for (not followed through a `$ref`). */
38
47
  readonly node: unknown;
48
+ /** The node's JSON Pointer, where the generator writes its type (`LocatedNode`). */
49
+ readonly location: string | undefined;
39
50
  readonly isSchema: boolean;
40
51
  /** The segment is a property or `$defs` name, not a keyword. */
41
52
  readonly isName: boolean;
53
+ /** The location of the map of names (`properties` or `$defs`) when `isName`. */
54
+ readonly namesLocation?: string | undefined;
55
+ /**
56
+ * The node is the `content` of a header or parameter typed as its first media type, so the
57
+ * next segment must name that media type and adds no member.
58
+ */
59
+ readonly isFirstMediaType?: boolean;
42
60
  readonly presence: Presence;
43
61
  }
44
62
 
@@ -58,19 +76,32 @@ function present(walk: TypedWalk): IrType {
58
76
  }
59
77
 
60
78
  function indexEntries(walk: TypedWalk, key: IrKeyword, keyword: string, refs: RefResolver) {
61
- const owner = followRefs(walk.node, refs);
62
- const child = isJsonObject(owner) ? owner[keyword] : undefined;
79
+ const owner = followLocated(walk, refs);
80
+ const child = isJsonObject(owner.node) ? owner.node[keyword] : undefined;
63
81
  return {
64
82
  ...walk,
65
83
  type: irIndexedAccess(present(walk), irKeyword(key)),
66
84
  node: child,
85
+ location: childLocation(owner.location, keyword),
67
86
  presence: schemaPresence(child, refs),
68
87
  };
69
88
  }
70
89
 
90
+ /**
91
+ * A property `--exclude-deprecated` left out has no member to index, and the `$Read<T>` or
92
+ * `$Write<T>` of a marked property has no member but the marker's: such a pointer is typed in
93
+ * place.
94
+ */
71
95
  function nameStep(walk: TypedWalk, name: string, isLast: boolean, input: TypedWalkInput) {
72
- const owner = followRefs(walk.node, input.refs);
73
- const properties = isJsonObject(owner) && isJsonObject(owner.properties) ? owner.properties : {};
96
+ const owner = followLocated(walk, input.refs);
97
+ const { node } = owner;
98
+ const properties = isJsonObject(node) && isJsonObject(node.properties) ? node.properties : {};
99
+ const value = properties[name];
100
+ const { options, refs } = input;
101
+ const isMarkedOnTheWay = !isLast && isMarkedProperty(value, options, refs);
102
+ if (isExcludedProperty(value, options, refs) || isMarkedOnTheWay) {
103
+ return null;
104
+ }
74
105
  const type =
75
106
  walk.presence === "present"
76
107
  ? indexKeyword(walk.type, name, isLast)
@@ -78,19 +109,25 @@ function nameStep(walk: TypedWalk, name: string, isLast: boolean, input: TypedWa
78
109
  return {
79
110
  ...walk,
80
111
  type,
81
- node: properties[name],
112
+ node: value,
113
+ location: childLocation(walk.namesLocation, name),
82
114
  isName: false,
83
- presence: propertyPresence(walk.node, name, input.options, input.refs),
115
+ presence: propertyPresence(walk, name, input.options, input.refs),
84
116
  };
85
117
  }
86
118
 
87
119
  /** `$defs` is optional (`NonNullable` when more follows); its members are present. */
88
- function defsStep(walk: TypedWalk, owner: unknown, isLast: boolean): TypedWalk {
89
- const defs = isJsonObject(owner) && isJsonObject(owner[DEFS_KEYWORD]) ? owner[DEFS_KEYWORD] : {};
120
+ function defsStep(walk: TypedWalk, owner: LocatedNode, isLast: boolean): TypedWalk {
121
+ const { node } = owner;
122
+ const defs = isJsonObject(node) && isJsonObject(node[DEFS_KEYWORD]) ? node[DEFS_KEYWORD] : {};
123
+ const namesLocation = childLocation(owner.location, DEFS_KEYWORD);
90
124
  return {
91
125
  ...walk,
92
126
  type: indexKeyword(present(walk), DEFS_KEYWORD, isLast),
127
+ // A node of its own, not in the document: its properties are the definitions.
93
128
  node: { properties: defs, required: Object.keys(defs) },
129
+ location: namesLocation,
130
+ namesLocation,
94
131
  isName: true,
95
132
  presence: "present",
96
133
  };
@@ -115,13 +152,13 @@ function hasKeywordMember(owner: unknown, segment: string): boolean {
115
152
  }
116
153
 
117
154
  function keywordStep(walk: TypedWalk, segment: string, isLast: boolean, input: TypedWalkInput) {
118
- const owner = followRefs(walk.node, input.refs);
119
- if (!hasKeywordMember(owner, segment)) {
155
+ const owner = followLocated(walk, input.refs);
156
+ if (!hasKeywordMember(owner.node, segment)) {
120
157
  return null;
121
158
  }
122
159
  switch (segment) {
123
160
  case "properties":
124
- return { ...walk, isName: true };
161
+ return { ...walk, isName: true, namesLocation: childLocation(owner.location, segment) };
125
162
  case "items":
126
163
  return indexEntries(walk, "number", "items", input.refs);
127
164
  case "additionalProperties":
@@ -143,10 +180,65 @@ function containerStep(walk: TypedWalk, index: number, input: TypedWalkInput): T
143
180
  ...walk,
144
181
  type: indexBy(present(walk), segment),
145
182
  node: isJsonObject(owner) ? owner[segment] : undefined,
183
+ location: childLocation(followLocated(walk, input.refs).location, segment),
146
184
  presence: isOptional ? "absent" : "present",
147
185
  };
148
186
  }
149
187
 
188
+ /**
189
+ * The `content` of a header or parameter: no member of its own, since the type holds its media
190
+ * types (or the first one's type). Untyped where the type is `string` or a `schema`'s, and as the
191
+ * last segment of the first media type form, whose type is not the map's.
192
+ */
193
+ function contentStep(walk: TypedWalk, form: ContentForm, isLast: boolean, refs: RefResolver) {
194
+ const isMapTyped = form === "media-type-members" || (form === "first-media-type" && !isLast);
195
+ if (!isMapTyped) {
196
+ return null;
197
+ }
198
+ const owner = followLocated(walk, refs);
199
+ return {
200
+ ...walk,
201
+ node: isJsonObject(owner.node) ? owner.node.content : undefined,
202
+ location: childLocation(owner.location, "content"),
203
+ isFirstMediaType: form === "first-media-type",
204
+ };
205
+ }
206
+
207
+ /** The first media type of such a `content` is the type itself; another one has no type. */
208
+ function firstMediaTypeStep(walk: TypedWalk, segment: string): TypedWalk | null {
209
+ const content = isJsonObject(walk.node) ? walk.node : {};
210
+ if (Object.keys(content)[0] !== segment) {
211
+ return null;
212
+ }
213
+ const location = childLocation(walk.location, segment);
214
+ return { ...walk, node: content[segment], location, isFirstMediaType: false };
215
+ }
216
+
217
+ function schemaStep(walk: TypedWalk, refs: RefResolver): TypedWalk {
218
+ const owner = followLocated(walk, refs);
219
+ const schema = isJsonObject(owner.node) ? owner.node.schema : undefined;
220
+ const location = childLocation(owner.location, "schema");
221
+ // The container's type is its schema's type: an optional header stays optional.
222
+ const presence = walk.presence === "absent" ? "absent" : schemaPresence(schema, refs);
223
+ return { ...walk, node: schema, location, isSchema: true, presence };
224
+ }
225
+
226
+ function documentStep(walk: TypedWalk, index: number, input: TypedWalkInput): TypedWalk | null {
227
+ const owner = followRefs(walk.node, input.refs);
228
+ const form = headerParameterContentForm(input.pointer, index, owner, input.options);
229
+ if (form !== null) {
230
+ const isLast = index === input.pointer.segments.length - 1;
231
+ return contentStep(walk, form, isLast, input.refs);
232
+ }
233
+ if (
234
+ isParameterList(input.pointer, index) ||
235
+ isExcludedOperation(input.pointer, index, owner, input)
236
+ ) {
237
+ return null;
238
+ }
239
+ return containerStep(walk, index, input);
240
+ }
241
+
150
242
  function step(walk: TypedWalk, index: number, input: TypedWalkInput): TypedWalk | null {
151
243
  const { segments } = input.pointer;
152
244
  const segment = segments[index] as string;
@@ -156,20 +248,19 @@ function step(walk: TypedWalk, index: number, input: TypedWalkInput): TypedWalk
156
248
  ? nameStep(walk, segment, isLast, input)
157
249
  : keywordStep(walk, segment, isLast, input);
158
250
  }
251
+ if (walk.isFirstMediaType) {
252
+ return firstMediaTypeStep(walk, segment);
253
+ }
159
254
  if (segment === "schema" && isTypedAsSchema(input.pointer, index)) {
160
- const owner = followRefs(walk.node, input.refs);
161
- const schema = isJsonObject(owner) ? owner.schema : undefined;
162
- // The container's type is its schema's type: an optional header stays optional.
163
- const presence = walk.presence === "absent" ? "absent" : schemaPresence(schema, input.refs);
164
- return { ...walk, node: schema, isSchema: true, presence };
255
+ return schemaStep(walk, input.refs);
165
256
  }
166
- return isParameterList(input.pointer, index) ? null : containerStep(walk, index, input);
257
+ return documentStep(walk, index, input);
167
258
  }
168
259
 
169
260
  /** Walk the whole pointer; see the module comment. */
170
261
  export function typedWalk(input: TypedWalkInput): TypedWalkResult {
171
262
  const { pointer } = input;
172
- const node = leadingNode(pointer, input.refs);
263
+ const { node, location } = leadingLocated(pointer, input.refs);
173
264
  const isSchema = startsInSchema(pointer);
174
265
  let walk: TypedWalk = {
175
266
  type: indexBy(
@@ -177,6 +268,7 @@ export function typedWalk(input: TypedWalkInput): TypedWalkResult {
177
268
  ...pointer.segments.slice(0, leadingSegmentsOf(pointer)),
178
269
  ),
179
270
  node,
271
+ location,
180
272
  isSchema,
181
273
  isName: false,
182
274
  presence: isSchema ? schemaPresence(node, input.refs) : "present",
@@ -30,7 +30,7 @@ function merged(left: AccessFlags, right: AccessFlags): AccessFlags {
30
30
  */
31
31
  function appliedFlags(
32
32
  schema: Schema | undefined,
33
- context: SchemaContext,
33
+ context: Pick<SchemaContext, "refs">,
34
34
  seen: ReadonlySet<Schema>,
35
35
  ): AccessFlags {
36
36
  if (typeof schema !== "object" || schema === null || seen.has(schema)) {
@@ -52,7 +52,10 @@ function appliedFlags(
52
52
  * it count, as in openapi-typescript; with it, also those of the schemas its `$ref` and `allOf`
53
53
  * apply (upstream #1938).
54
54
  */
55
- export function propertyAccessFlags(value: Schema, context: SchemaContext): AccessFlags {
55
+ export function propertyAccessFlags(
56
+ value: Schema,
57
+ context: Pick<SchemaContext, "options" | "refs">,
58
+ ): AccessFlags {
56
59
  if (typeof value !== "object") {
57
60
  return NO_FLAGS;
58
61
  }
@@ -0,0 +1,31 @@
1
+ import type { IrType } from "../../../domain/ir/nodes.js";
2
+ import { NO_DECLARED_TYPES } from "../document/declared-types.js";
3
+ import { assignabilityProof } from "../document/type-assignability.js";
4
+ import { unionOf } from "../type-combinators.js";
5
+
6
+ /**
7
+ * The index signature type with the property types it does not admit. TypeScript checks an index
8
+ * signature against every key of a value, declared ones too, so `{ total: number } & { [key:
9
+ * string]: string }` admits no value with `total` (with a required `total`, none at all). The
10
+ * signature takes those property types too, as `unevaluated-properties` does. A property type
11
+ * proven assignable adds nothing, so output that admitted its values stays; one the proof cannot
12
+ * decide (a `$ref`) is added, member by member, so the union stays flat. `$defs` is a keyword, not
13
+ * data, so it is not read.
14
+ */
15
+ export function withPropertyTypes(indexType: IrType, propertyTypes: readonly IrType[]): IrType {
16
+ const relate = assignabilityProof(NO_DECLARED_TYPES);
17
+ const members = unionMembers(indexType);
18
+ const added: IrType[] = [];
19
+ // Each member of a property type joins the union on its own, unless the union admits it
20
+ // already: `(string | null) | (string | null)` is written `string | null`.
21
+ for (const member of propertyTypes.flatMap(unionMembers)) {
22
+ if (relate(member, unionOf([...members, ...added])) !== "assignable") {
23
+ added.push(member);
24
+ }
25
+ }
26
+ // Without an added member the index type stays as printed before, repeats and all.
27
+ return added.length === 0 ? indexType : unionOf([...members, ...added]);
28
+ }
29
+
30
+ const unionMembers = (type: IrType): readonly IrType[] =>
31
+ type.kind === "union" ? type.types : [type];
@@ -0,0 +1,35 @@
1
+ import { placeOf } from "../../../domain/openapi/document-places.js";
2
+ import { parseRefPointer } from "../../../domain/openapi/json-pointer.js";
3
+ import { FIX, type OutputFix } from "../../../domain/output-fixes.js";
4
+
5
+ /** The words that mark an input anywhere in a location, as the original reads them. */
6
+ const ORIGINAL_INPUT_WORDS = ["parameters", "requestBody", "requestBodies"] as const;
7
+
8
+ /**
9
+ * Whether a schema location describes an input: a parameter or a request body. A client may
10
+ * leave out an input property that has a `default`, so the default makes no such property
11
+ * required. The original tests the location text for a word, so a schema or path that is only
12
+ * named so (`#/components/schemas/Job/properties/parameters`, `/system-parameters`) counts as an
13
+ * input too. The `default-input-locations` fix reads a pointer into the document by the
14
+ * document's structure instead (ADR-021).
15
+ */
16
+ export function isInputLocation(path: string | undefined, fixes: ReadonlySet<OutputFix>): boolean {
17
+ if (path === undefined) {
18
+ return false;
19
+ }
20
+ return fixes.has(FIX.DEFAULT_INPUT_LOCATIONS) ? isStructuralInput(path) : namesInput(path);
21
+ }
22
+
23
+ function namesInput(path: string): boolean {
24
+ return ORIGINAL_INPUT_WORDS.some((word) => path.includes(word));
25
+ }
26
+
27
+ /**
28
+ * Whether the document's structure makes the pointer an input. A path an exported transformer was
29
+ * given that is no pointer into the document (`/items/{id}`) has no known structure, so the
30
+ * original's words still mark an input there.
31
+ */
32
+ function isStructuralInput(path: string): boolean {
33
+ const place = placeOf(parseRefPointer(path));
34
+ return place === "outside" ? namesInput(path) : place === "input";
35
+ }
@@ -15,13 +15,13 @@ import { schemaCommentOptionsOf, withSchemaComment } from "../schema-comment.js"
15
15
  import { UNKNOWN_TYPE } from "../shared-types.js";
16
16
  import { intersectionOf, unionOf } from "../type-combinators.js";
17
17
  import { type AccessFlags, propertyAccessFlags } from "./access-flags.js";
18
+ import { withPropertyTypes } from "./index-signature-type.js";
18
19
  import { discriminatorMember, inheritedDiscriminator } from "./inherited-discriminator.js";
20
+ import { isInputLocation } from "./input-location.js";
21
+ import { refWithSiblingsAsAllOf } from "./ref-siblings.js";
19
22
  import type { SchemaOptions } from "./schema-context.js";
20
23
  import type { SchemaRequest } from "./schema-rule.js";
21
24
 
22
- /** Path parts where a default value cannot make a property required (inputs may omit it). */
23
- const INPUT_PATH_PARTS = ["parameters", "requestBody", "requestBodies"] as const;
24
-
25
25
  export class InvalidPropertyError extends OpenAPITypesafeError {
26
26
  constructor(path: string | undefined, key: string, value: unknown) {
27
27
  const received = Array.isArray(value) ? "Array" : typeof value;
@@ -85,11 +85,10 @@ function isOptionalProperty(
85
85
  request: SchemaRequest,
86
86
  ): boolean {
87
87
  const { options } = request.context;
88
- const path = request.location.path;
89
88
  const isDefaultRequired =
90
89
  facts.hasDefault &&
91
90
  options.defaultNonNullable &&
92
- !INPUT_PATH_PARTS.some((part) => path?.includes(part));
91
+ !isInputLocation(request.location.path, options.fixes);
93
92
  const isRequired =
94
93
  schema.required?.includes(key) ||
95
94
  (schema.required === undefined && options.propertiesRequiredByDefault) ||
@@ -111,6 +110,12 @@ interface CustomResult {
111
110
  readonly isOptional: true | null;
112
111
  }
113
112
 
113
+ /** A `$ref` with siblings that `ref-siblings` types as an `allOf` is typed as a schema. */
114
+ function plainRef({ ref }: PropertyFacts, value: Schema, request: SchemaRequest) {
115
+ const isAllOf = ref && refWithSiblingsAsAllOf(value as SchemaObject, request.context.options);
116
+ return isAllOf ? undefined : ref;
117
+ }
118
+
114
119
  function propertyType(
115
120
  key: string,
116
121
  value: Schema,
@@ -118,8 +123,9 @@ function propertyType(
118
123
  request: SchemaRequest,
119
124
  ): CustomResult {
120
125
  const path = appendPointer(request.location.path, [key]);
121
- const type = facts.ref
122
- ? schemaRefType(facts.ref, request)
126
+ const ref = plainRef(facts, value, request);
127
+ const type = ref
128
+ ? schemaRefType(ref, request)
123
129
  : request.transformNested(value, { ...request.location, path });
124
130
  const custom = request.context.callbacks.transform(value, request.location);
125
131
  return custom === null
@@ -270,6 +276,9 @@ export function objectType(schema: SchemaObject, request: SchemaRequest): IrType
270
276
  return inherited ? irObject([discriminatorMember(inherited, request)]) : null;
271
277
  }
272
278
  const members = discriminatorAndPropertyMembers(schema, request);
279
+ const propertyTypes = members.flatMap((member) =>
280
+ member.kind === "property" ? [member.type] : [],
281
+ );
273
282
  // As in the original, a null `$defs` throws here (Object.keys) instead of being skipped.
274
283
  if (
275
284
  "$defs" in schema &&
@@ -282,8 +291,9 @@ export function objectType(schema: SchemaObject, request: SchemaRequest): IrType
282
291
  if (valueTypes.length === 0) {
283
292
  return members.length > 0 ? irObject(members) : null;
284
293
  }
294
+ const indexType = withPropertyTypes(unionOf(valueTypes), propertyTypes);
285
295
  const index = irObject([
286
- irStringIndexSignature("key", unionOf(valueTypes), request.context.options.immutable),
296
+ irStringIndexSignature("key", indexType, request.context.options.immutable),
287
297
  ]);
288
298
  return intersectionOf([...(members.length > 0 ? [irObject(members)] : []), index]);
289
299
  }
@@ -32,7 +32,8 @@ const SHAPE_RULES: readonly SchemaRule[] = [
32
32
  },
33
33
  {
34
34
  name: "const",
35
- matches: (schema) => schema.const !== null && schema.const !== undefined,
35
+ // `const: null` is the value null (JSON Schema §6.1.3); the original read it as no `const`.
36
+ matches: (schema) => schema.const !== undefined,
36
37
  transform: (schema) => literalType(schema.const),
37
38
  },
38
39
  enumRule,
@@ -1,13 +1,8 @@
1
- import {
2
- irKeyword,
3
- irObject,
4
- irReference,
5
- irStringIndexSignature,
6
- } from "../../../domain/ir/constructors.js";
7
- import type { IrMember, IrType } from "../../../domain/ir/nodes.js";
1
+ import { irObject, irStringIndexSignature } from "../../../domain/ir/constructors.js";
2
+ import type { IrType } from "../../../domain/ir/nodes.js";
8
3
  import type { Schema, SchemaObject } from "../../../domain/openapi/schema-object.js";
9
4
  import { FIX } from "../../../domain/output-fixes.js";
10
- import { HELPER_TYPES } from "../helper-types.js";
5
+ import { declaredValueTypes } from "../declared-value-types.js";
11
6
  import { UNKNOWN_TYPE } from "../shared-types.js";
12
7
  import { unionOf } from "../type-combinators.js";
13
8
  import type { SchemaRequest } from "./schema-rule.js";
@@ -46,41 +41,6 @@ function isAnySchema(schema: Schema): boolean {
46
41
  return schema === true || (typeof schema === "object" && Object.keys(schema).length === 0);
47
42
  }
48
43
 
49
- function memberValueTypes(member: IrMember): IrType[] {
50
- if (member.kind === "indexSignature") {
51
- return [member.type];
52
- }
53
- return member.isOptional ? [member.type, irKeyword("undefined")] : [member.type];
54
- }
55
-
56
- /**
57
- * The value types a key of the composed type can have, read from its IR: the members of object
58
- * types, through unions and intersections. A reference (a `$ref`, `Omit`, `WithRequired`) or a raw
59
- * type is read with `PropertyValues`. Primitives, literals and arrays declare no keys.
60
- */
61
- function declaredValueTypes(type: IrType, request: SchemaRequest): IrType[] {
62
- switch (type.kind) {
63
- case "object":
64
- return type.members.flatMap(memberValueTypes);
65
- case "union":
66
- case "intersection":
67
- return type.types.flatMap((member) => declaredValueTypes(member, request));
68
- case "reference":
69
- case "indexedAccess":
70
- case "raw":
71
- request.context.footer.requirePropertyValues();
72
- return [irReference(HELPER_TYPES.propertyValues.name, [type])];
73
- case "keyword":
74
- case "stringLiteral":
75
- case "numberLiteral":
76
- case "booleanLiteral":
77
- case "array":
78
- case "tuple":
79
- case "readonly":
80
- return [];
81
- }
82
- }
83
-
84
44
  /**
85
45
  * The index signature object for `unevaluatedProperties`, given the composed type of the other
86
46
  * keywords (null when there is none). Null when the fix is off or the keyword does not apply.
@@ -98,7 +58,7 @@ export function unevaluatedPropertiesType(
98
58
  ? [UNKNOWN_TYPE]
99
59
  : [
100
60
  request.transformNested(unevaluated, request.location, true),
101
- ...(composed === null ? [] : declaredValueTypes(composed, request)),
61
+ ...(composed === null ? [] : declaredValueTypes(composed, request.context.footer)),
102
62
  ];
103
63
  const { immutable } = request.context.options;
104
64
  return irObject([irStringIndexSignature("key", unionOf(valueTypes), immutable)]);
@@ -0,0 +1,17 @@
1
+ import type { DebugGroup, Logger } from "../application/ports/logger.js";
2
+
3
+ /**
4
+ * A logger whose lines name the API they are about. With several APIs in `redocly.yaml`, each is
5
+ * loaded and linted in turn, and a problem line such as "Can't resolve $ref" is the same for
6
+ * each, so without the name the user cannot tell which API it belongs to. The summary of a
7
+ * failed API names it the same way (`API a: …`).
8
+ */
9
+ export function apiLogger(logger: Logger, apiName: string): Logger {
10
+ const prefix = `API ${apiName}: `;
11
+ return {
12
+ debug: (message: string, group?: DebugGroup, durationMs?: number) =>
13
+ logger.debug(`${prefix}${message}`, group, durationMs),
14
+ warn: (message: string) => logger.warn(`${prefix}${message}`),
15
+ error: (message: string) => logger.error(`${prefix}${message}`),
16
+ };
17
+ }
@@ -1,12 +1,10 @@
1
1
  import { kebabCase } from "scule";
2
- import { isOpenAPITypesafeError, OpenAPITypesafeError } from "../domain/errors.js";
3
2
  import type { OutputFixName } from "../domain/output-fixes.js";
4
3
  import { WARNING_CODES, warningText } from "../domain/warnings.js";
5
4
  import { GENERATOR_FLAGS, type GeneratorFlags, hostList } from "./cli-arguments.js";
6
- import { cliError } from "./cli-errors.js";
7
- import { resolveLocationUrl } from "./cli-output.js";
5
+ import { ApiFailure, cliError } from "./cli-errors.js";
8
6
  import { fixNamesOf } from "./fix-option.js";
9
- import type { RefLimitOptions } from "./ref-limits.js";
7
+ import { localRootUrl, type RefLimitOptions } from "./ref-limits.js";
10
8
 
11
9
  /**
12
10
  * The settings of one API in `redocly.yaml` (`apis.<name>.x-openapi-ts`). A setting has the name
@@ -23,6 +21,7 @@ export const OUTPUT_SETTING = "output";
23
21
  export const FIX_SETTING = "fix";
24
22
 
25
23
  const LOCAL_REF_ROOT_SETTING = "localRefRoot";
24
+ const LOCAL_ROOT_EXPECTED = "a directory path or a file: URL";
26
25
  const REMOTE_REF_HOSTS_SETTING = "remoteRefHosts";
27
26
 
28
27
  /** `--content-never` is accepted and has no effect, as on the command line. */
@@ -127,31 +126,24 @@ export function apiFixNames(
127
126
  try {
128
127
  return fixNamesOf(entries);
129
128
  } catch (error) {
130
- throw isOpenAPITypesafeError(error) ? withApiName(apiName, error) : error;
129
+ throw new ApiFailure(apiName, error);
131
130
  }
132
131
  }
133
132
 
134
- /** The same error, with the API named first in its message. */
135
- function withApiName(apiName: string, error: OpenAPITypesafeError): OpenAPITypesafeError {
136
- const { code, details, cause } = error;
137
- return new OpenAPITypesafeError({
138
- code,
139
- message: `API ${apiName}: ${error.message}`,
140
- details,
141
- cause,
142
- });
143
- }
144
-
145
- /** `local-ref-root: dir`, relative to the config file; empty means the config file's directory. */
133
+ /**
134
+ * `local-ref-root: dir`, relative to the config file; empty means the config file's directory. A
135
+ * value that is no text or a URL of another protocol fails with `CLI_CONFIG_INVALID`.
136
+ */
146
137
  function localRefRootOf(apiName: string, settings: ApiSettings, configRoot: URL): RefLimitOptions {
147
138
  if (!hasSetting(settings, LOCAL_REF_ROOT_SETTING)) {
148
139
  return {};
149
140
  }
150
141
  const value = settingValue(settings, LOCAL_REF_ROOT_SETTING) ?? "";
151
- if (typeof value !== "string") {
152
- throw invalidSetting(apiName, settings, LOCAL_REF_ROOT_SETTING, "a directory path");
142
+ const localRoot = typeof value === "string" ? localRootUrl(value, configRoot) : undefined;
143
+ if (localRoot === undefined) {
144
+ throw invalidSetting(apiName, settings, LOCAL_REF_ROOT_SETTING, LOCAL_ROOT_EXPECTED);
153
145
  }
154
- return { localRefRoot: resolveLocationUrl(value === "" ? "." : value, configRoot) };
146
+ return { localRefRoot: localRoot };
155
147
  }
156
148
 
157
149
  /** `remote-ref-hosts: [a, b]` or `a,b`; an empty list or value allows no host. */