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

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 (448) hide show
  1. package/CHANGELOG.md +1831 -0
  2. package/dist/adapters/node-file-system/file-claims.cjs +105 -0
  3. package/dist/adapters/node-file-system/file-claims.cjs.map +1 -0
  4. package/dist/adapters/node-file-system/file-claims.mjs +95 -0
  5. package/dist/adapters/node-file-system/file-claims.mjs.map +1 -0
  6. package/dist/adapters/node-file-system/file-identity.cjs +82 -0
  7. package/dist/adapters/node-file-system/file-identity.cjs.map +1 -0
  8. package/dist/adapters/node-file-system/file-identity.mjs +74 -0
  9. package/dist/adapters/node-file-system/file-identity.mjs.map +1 -0
  10. package/dist/adapters/node-file-system/node-file-system.cjs +90 -14
  11. package/dist/adapters/node-file-system/node-file-system.cjs.map +1 -1
  12. package/dist/adapters/node-file-system/node-file-system.mjs +90 -14
  13. package/dist/adapters/node-file-system/node-file-system.mjs.map +1 -1
  14. package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs +75 -0
  15. package/dist/adapters/redocly-loader/bundled-mapping-copies.cjs.map +1 -0
  16. package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs +72 -0
  17. package/dist/adapters/redocly-loader/bundled-mapping-copies.mjs.map +1 -0
  18. package/dist/adapters/redocly-loader/bundled-ref-repair.cjs +1 -1
  19. package/dist/adapters/redocly-loader/bundled-ref-repair.cjs.map +1 -1
  20. package/dist/adapters/redocly-loader/bundled-ref-repair.mjs +2 -2
  21. package/dist/adapters/redocly-loader/bundled-ref-repair.mjs.map +1 -1
  22. package/dist/adapters/redocly-loader/loaded-documents.cjs +28 -0
  23. package/dist/adapters/redocly-loader/loaded-documents.cjs.map +1 -0
  24. package/dist/adapters/redocly-loader/loaded-documents.mjs +21 -0
  25. package/dist/adapters/redocly-loader/loaded-documents.mjs.map +1 -0
  26. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +14 -15
  27. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  28. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +12 -13
  29. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  30. package/dist/adapters/redocly-loader/ref-access.cjs +31 -3
  31. package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
  32. package/dist/adapters/redocly-loader/ref-access.mjs +31 -3
  33. package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
  34. package/dist/adapters/redocly-loader/ref-holders.cjs +2 -2
  35. package/dist/adapters/redocly-loader/ref-holders.cjs.map +1 -1
  36. package/dist/adapters/redocly-loader/ref-holders.mjs +3 -3
  37. package/dist/adapters/redocly-loader/ref-holders.mjs.map +1 -1
  38. package/dist/adapters/redocly-loader/remote-body-limit.cjs +50 -0
  39. package/dist/adapters/redocly-loader/remote-body-limit.cjs.map +1 -0
  40. package/dist/adapters/redocly-loader/remote-body-limit.mjs +48 -0
  41. package/dist/adapters/redocly-loader/remote-body-limit.mjs.map +1 -0
  42. package/dist/adapters/redocly-loader/remote-fetch.cjs +35 -6
  43. package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
  44. package/dist/adapters/redocly-loader/remote-fetch.mjs +35 -6
  45. package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
  46. package/dist/adapters/redocly-loader/remote-node-limit.cjs +47 -0
  47. package/dist/adapters/redocly-loader/remote-node-limit.cjs.map +1 -0
  48. package/dist/adapters/redocly-loader/remote-node-limit.mjs +44 -0
  49. package/dist/adapters/redocly-loader/remote-node-limit.mjs.map +1 -0
  50. package/dist/adapters/redocly-loader/schema-load-errors.cjs +32 -8
  51. package/dist/adapters/redocly-loader/schema-load-errors.cjs.map +1 -1
  52. package/dist/adapters/redocly-loader/schema-load-errors.mjs +32 -9
  53. package/dist/adapters/redocly-loader/schema-load-errors.mjs.map +1 -1
  54. package/dist/adapters/redocly-loader/schema-source.cjs +11 -7
  55. package/dist/adapters/redocly-loader/schema-source.cjs.map +1 -1
  56. package/dist/adapters/redocly-loader/schema-source.mjs +12 -8
  57. package/dist/adapters/redocly-loader/schema-source.mjs.map +1 -1
  58. package/dist/adapters/redocly-loader/unsupported-keys.cjs +3 -5
  59. package/dist/adapters/redocly-loader/unsupported-keys.cjs.map +1 -1
  60. package/dist/adapters/redocly-loader/unsupported-keys.mjs +3 -5
  61. package/dist/adapters/redocly-loader/unsupported-keys.mjs.map +1 -1
  62. package/dist/adapters/terminal-logger/terminal-logger.cjs +6 -4
  63. package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
  64. package/dist/adapters/terminal-logger/terminal-logger.mjs +6 -4
  65. package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
  66. package/dist/api/default-ports.cjs +14 -2
  67. package/dist/api/default-ports.cjs.map +1 -1
  68. package/dist/api/default-ports.mjs +14 -2
  69. package/dist/api/default-ports.mjs.map +1 -1
  70. package/dist/api/discriminator-enum-parent.cjs +2 -2
  71. package/dist/api/discriminator-enum-parent.cjs.map +1 -1
  72. package/dist/api/discriminator-enum-parent.mjs +3 -3
  73. package/dist/api/discriminator-enum-parent.mjs.map +1 -1
  74. package/dist/api/discriminator-other-owners.cjs +5 -3
  75. package/dist/api/discriminator-other-owners.cjs.map +1 -1
  76. package/dist/api/discriminator-other-owners.mjs +5 -3
  77. package/dist/api/discriminator-other-owners.mjs.map +1 -1
  78. package/dist/api/discriminator-patch.cjs +5 -1
  79. package/dist/api/discriminator-patch.cjs.map +1 -1
  80. package/dist/api/discriminator-patch.mjs +5 -1
  81. package/dist/api/discriminator-patch.mjs.map +1 -1
  82. package/dist/api/discriminator-scan.cjs +15 -8
  83. package/dist/api/discriminator-scan.cjs.map +1 -1
  84. package/dist/api/discriminator-scan.mjs +15 -8
  85. package/dist/api/discriminator-scan.mjs.map +1 -1
  86. package/dist/api/document-run.cjs +2 -2
  87. package/dist/api/document-run.cjs.map +1 -1
  88. package/dist/api/document-run.mjs +2 -2
  89. package/dist/api/document-run.mjs.map +1 -1
  90. package/dist/api/legacy-utils.cjs.map +1 -1
  91. package/dist/api/legacy-utils.mjs.map +1 -1
  92. package/dist/api/openapi-ts.cjs +8 -4
  93. package/dist/api/openapi-ts.cjs.map +1 -1
  94. package/dist/api/openapi-ts.mjs +8 -5
  95. package/dist/api/openapi-ts.mjs.map +1 -1
  96. package/dist/api/schema-callbacks.cjs +36 -12
  97. package/dist/api/schema-callbacks.cjs.map +1 -1
  98. package/dist/api/schema-callbacks.mjs +36 -12
  99. package/dist/api/schema-callbacks.mjs.map +1 -1
  100. package/dist/api/transform-context.cjs +7 -3
  101. package/dist/api/transform-context.cjs.map +1 -1
  102. package/dist/api/transform-context.mjs +7 -3
  103. package/dist/api/transform-context.mjs.map +1 -1
  104. package/dist/api/transform-schema.cjs +40 -10
  105. package/dist/api/transform-schema.cjs.map +1 -1
  106. package/dist/api/transform-schema.mjs +41 -11
  107. package/dist/api/transform-schema.mjs.map +1 -1
  108. package/dist/application/print/text-writer.cjs +10 -3
  109. package/dist/application/print/text-writer.cjs.map +1 -1
  110. package/dist/application/print/text-writer.mjs +10 -3
  111. package/dist/application/print/text-writer.mjs.map +1 -1
  112. package/dist/application/stack-overflow.cjs +2 -1
  113. package/dist/application/stack-overflow.cjs.map +1 -1
  114. package/dist/application/stack-overflow.mjs +2 -1
  115. package/dist/application/stack-overflow.mjs.map +1 -1
  116. package/dist/application/transform/document/components.cjs +1 -1
  117. package/dist/application/transform/document/components.cjs.map +1 -1
  118. package/dist/application/transform/document/components.mjs +2 -2
  119. package/dist/application/transform/document/components.mjs.map +1 -1
  120. package/dist/application/transform/document/declared-type-names.cjs +23 -0
  121. package/dist/application/transform/document/declared-type-names.cjs.map +1 -0
  122. package/dist/application/transform/document/declared-type-names.mjs +20 -0
  123. package/dist/application/transform/document/declared-type-names.mjs.map +1 -0
  124. package/dist/application/transform/document/defs-root.cjs +1 -1
  125. package/dist/application/transform/document/defs-root.cjs.map +1 -1
  126. package/dist/application/transform/document/defs-root.mjs +2 -2
  127. package/dist/application/transform/document/defs-root.mjs.map +1 -1
  128. package/dist/application/transform/document/document.cjs +4 -1
  129. package/dist/application/transform/document/document.cjs.map +1 -1
  130. package/dist/application/transform/document/document.mjs +3 -2
  131. package/dist/application/transform/document/document.mjs.map +1 -1
  132. package/dist/application/transform/document/enum-names-given-back.cjs +62 -0
  133. package/dist/application/transform/document/enum-names-given-back.cjs.map +1 -0
  134. package/dist/application/transform/document/enum-names-given-back.mjs +58 -0
  135. package/dist/application/transform/document/enum-names-given-back.mjs.map +1 -0
  136. package/dist/application/transform/document/global-type-references.cjs +1 -0
  137. package/dist/application/transform/document/global-type-references.cjs.map +1 -1
  138. package/dist/application/transform/document/global-type-references.mjs +1 -1
  139. package/dist/application/transform/document/global-type-references.mjs.map +1 -1
  140. package/dist/application/transform/document/media-type.cjs +2 -2
  141. package/dist/application/transform/document/media-type.cjs.map +1 -1
  142. package/dist/application/transform/document/media-type.mjs +3 -3
  143. package/dist/application/transform/document/media-type.mjs.map +1 -1
  144. package/dist/application/transform/document/operation-keys-written.cjs +26 -0
  145. package/dist/application/transform/document/operation-keys-written.cjs.map +1 -0
  146. package/dist/application/transform/document/operation-keys-written.mjs +24 -0
  147. package/dist/application/transform/document/operation-keys-written.mjs.map +1 -0
  148. package/dist/application/transform/document/operation.cjs +1 -1
  149. package/dist/application/transform/document/operation.cjs.map +1 -1
  150. package/dist/application/transform/document/operation.mjs +2 -2
  151. package/dist/application/transform/document/operation.mjs.map +1 -1
  152. package/dist/application/transform/document/parameter-entries.cjs +34 -0
  153. package/dist/application/transform/document/parameter-entries.cjs.map +1 -0
  154. package/dist/application/transform/document/parameter-entries.mjs +30 -0
  155. package/dist/application/transform/document/parameter-entries.mjs.map +1 -0
  156. package/dist/application/transform/document/parameter.cjs +12 -4
  157. package/dist/application/transform/document/parameter.cjs.map +1 -1
  158. package/dist/application/transform/document/parameter.mjs +13 -5
  159. package/dist/application/transform/document/parameter.mjs.map +1 -1
  160. package/dist/application/transform/document/parameters.cjs +6 -29
  161. package/dist/application/transform/document/parameters.cjs.map +1 -1
  162. package/dist/application/transform/document/parameters.mjs +6 -29
  163. package/dist/application/transform/document/parameters.mjs.map +1 -1
  164. package/dist/application/transform/document/path-item.cjs +11 -14
  165. package/dist/application/transform/document/path-item.cjs.map +1 -1
  166. package/dist/application/transform/document/path-item.mjs +12 -16
  167. package/dist/application/transform/document/path-item.mjs.map +1 -1
  168. package/dist/application/transform/document/paths-enum.cjs +1 -0
  169. package/dist/application/transform/document/paths-enum.cjs.map +1 -1
  170. package/dist/application/transform/document/paths-enum.mjs +1 -1
  171. package/dist/application/transform/document/paths-enum.mjs.map +1 -1
  172. package/dist/application/transform/document/paths.cjs +5 -1
  173. package/dist/application/transform/document/paths.cjs.map +1 -1
  174. package/dist/application/transform/document/paths.mjs +6 -2
  175. package/dist/application/transform/document/paths.mjs.map +1 -1
  176. package/dist/application/transform/document/referenced-parameter.cjs +80 -0
  177. package/dist/application/transform/document/referenced-parameter.cjs.map +1 -0
  178. package/dist/application/transform/document/referenced-parameter.mjs +78 -0
  179. package/dist/application/transform/document/referenced-parameter.mjs.map +1 -0
  180. package/dist/application/transform/document/request-body.cjs +1 -1
  181. package/dist/application/transform/document/request-body.cjs.map +1 -1
  182. package/dist/application/transform/document/request-body.mjs +2 -2
  183. package/dist/application/transform/document/request-body.mjs.map +1 -1
  184. package/dist/application/transform/document/response.cjs +2 -2
  185. package/dist/application/transform/document/response.cjs.map +1 -1
  186. package/dist/application/transform/document/response.mjs +3 -3
  187. package/dist/application/transform/document/response.mjs.map +1 -1
  188. package/dist/application/transform/document/responses.cjs +1 -1
  189. package/dist/application/transform/document/responses.cjs.map +1 -1
  190. package/dist/application/transform/document/responses.mjs +2 -2
  191. package/dist/application/transform/document/responses.mjs.map +1 -1
  192. package/dist/application/transform/document/root-type-aliases.cjs +8 -23
  193. package/dist/application/transform/document/root-type-aliases.cjs.map +1 -1
  194. package/dist/application/transform/document/root-type-aliases.mjs +8 -24
  195. package/dist/application/transform/document/root-type-aliases.mjs.map +1 -1
  196. package/dist/application/transform/document/type-resolution.cjs +3 -1
  197. package/dist/application/transform/document/type-resolution.cjs.map +1 -1
  198. package/dist/application/transform/document/type-resolution.mjs +3 -1
  199. package/dist/application/transform/document/type-resolution.mjs.map +1 -1
  200. package/dist/application/transform/document/webhooks.cjs +1 -1
  201. package/dist/application/transform/document/webhooks.cjs.map +1 -1
  202. package/dist/application/transform/document/webhooks.mjs +2 -2
  203. package/dist/application/transform/document/webhooks.mjs.map +1 -1
  204. package/dist/application/transform/footer/document-footer.cjs +10 -2
  205. package/dist/application/transform/footer/document-footer.cjs.map +1 -1
  206. package/dist/application/transform/footer/document-footer.mjs +10 -2
  207. package/dist/application/transform/footer/document-footer.mjs.map +1 -1
  208. package/dist/application/transform/footer/enum-name-claims.cjs +48 -0
  209. package/dist/application/transform/footer/enum-name-claims.cjs.map +1 -0
  210. package/dist/application/transform/footer/enum-name-claims.mjs +46 -0
  211. package/dist/application/transform/footer/enum-name-claims.mjs.map +1 -0
  212. package/dist/application/transform/footer/operation-keys.cjs +23 -0
  213. package/dist/application/transform/footer/operation-keys.cjs.map +1 -0
  214. package/dist/application/transform/footer/operation-keys.mjs +21 -0
  215. package/dist/application/transform/footer/operation-keys.mjs.map +1 -0
  216. package/dist/application/transform/footer/type-evaluation.cjs +4 -4
  217. package/dist/application/transform/footer/type-evaluation.cjs.map +1 -1
  218. package/dist/application/transform/footer/type-evaluation.mjs +4 -4
  219. package/dist/application/transform/footer/type-evaluation.mjs.map +1 -1
  220. package/dist/application/transform/helper-types.cjs +1 -1
  221. package/dist/application/transform/helper-types.cjs.map +1 -1
  222. package/dist/application/transform/helper-types.mjs +1 -1
  223. package/dist/application/transform/helper-types.mjs.map +1 -1
  224. package/dist/application/transform/numbered-names.cjs +9 -0
  225. package/dist/application/transform/numbered-names.cjs.map +1 -1
  226. package/dist/application/transform/numbered-names.mjs +9 -1
  227. package/dist/application/transform/numbered-names.mjs.map +1 -1
  228. package/dist/application/transform/ref-pointer/generated-location.cjs +1 -1
  229. package/dist/application/transform/ref-pointer/generated-location.cjs.map +1 -1
  230. package/dist/application/transform/ref-pointer/generated-location.mjs +2 -2
  231. package/dist/application/transform/ref-pointer/generated-location.mjs.map +1 -1
  232. package/dist/application/transform/ref-type.cjs +2 -1
  233. package/dist/application/transform/ref-type.cjs.map +1 -1
  234. package/dist/application/transform/ref-type.mjs +2 -1
  235. package/dist/application/transform/ref-type.mjs.map +1 -1
  236. package/dist/application/transform/schema/composition-type.cjs +1 -1
  237. package/dist/application/transform/schema/composition-type.cjs.map +1 -1
  238. package/dist/application/transform/schema/composition-type.mjs +2 -2
  239. package/dist/application/transform/schema/composition-type.mjs.map +1 -1
  240. package/dist/application/transform/schema/enum-rule.cjs +1 -1
  241. package/dist/application/transform/schema/enum-rule.cjs.map +1 -1
  242. package/dist/application/transform/schema/enum-rule.mjs +2 -2
  243. package/dist/application/transform/schema/enum-rule.mjs.map +1 -1
  244. package/dist/application/transform/schema/object-type.cjs +3 -3
  245. package/dist/application/transform/schema/object-type.cjs.map +1 -1
  246. package/dist/application/transform/schema/object-type.mjs +4 -4
  247. package/dist/application/transform/schema/object-type.mjs.map +1 -1
  248. package/dist/application/transform/schema/ref-siblings.cjs +47 -0
  249. package/dist/application/transform/schema/ref-siblings.cjs.map +1 -0
  250. package/dist/application/transform/schema/ref-siblings.mjs +45 -0
  251. package/dist/application/transform/schema/ref-siblings.mjs.map +1 -0
  252. package/dist/application/transform/schema/schema-transform.cjs +4 -2
  253. package/dist/application/transform/schema/schema-transform.cjs.map +1 -1
  254. package/dist/application/transform/schema/schema-transform.mjs +4 -2
  255. package/dist/application/transform/schema/schema-transform.mjs.map +1 -1
  256. package/dist/application/transform/schema-comment.cjs +5 -1
  257. package/dist/application/transform/schema-comment.cjs.map +1 -1
  258. package/dist/application/transform/schema-comment.mjs +5 -1
  259. package/dist/application/transform/schema-comment.mjs.map +1 -1
  260. package/dist/cli/api-settings.cjs +62 -6
  261. package/dist/cli/api-settings.cjs.map +1 -1
  262. package/dist/cli/api-settings.mjs +60 -7
  263. package/dist/cli/api-settings.mjs.map +1 -1
  264. package/dist/cli/api-writes.cjs +72 -0
  265. package/dist/cli/api-writes.cjs.map +1 -0
  266. package/dist/cli/api-writes.mjs +69 -0
  267. package/dist/cli/api-writes.mjs.map +1 -0
  268. package/dist/cli/cli-errors.cjs +12 -1
  269. package/dist/cli/cli-errors.cjs.map +1 -1
  270. package/dist/cli/cli-errors.mjs +12 -1
  271. package/dist/cli/cli-errors.mjs.map +1 -1
  272. package/dist/cli/cli-output.cjs +106 -1
  273. package/dist/cli/cli-output.cjs.map +1 -1
  274. package/dist/cli/cli-output.mjs +99 -2
  275. package/dist/cli/cli-output.mjs.map +1 -1
  276. package/dist/cli/main.cjs +5 -1
  277. package/dist/cli/main.cjs.map +1 -1
  278. package/dist/cli/main.mjs +6 -2
  279. package/dist/cli/main.mjs.map +1 -1
  280. package/dist/cli/redocly-apis.cjs +82 -36
  281. package/dist/cli/redocly-apis.cjs.map +1 -1
  282. package/dist/cli/redocly-apis.mjs +85 -39
  283. package/dist/cli/redocly-apis.mjs.map +1 -1
  284. package/dist/cli/run-cli.cjs +13 -7
  285. package/dist/cli/run-cli.cjs.map +1 -1
  286. package/dist/cli/run-cli.mjs +14 -8
  287. package/dist/cli/run-cli.mjs.map +1 -1
  288. package/dist/domain/append-all.cjs +10 -0
  289. package/dist/domain/append-all.cjs.map +1 -0
  290. package/dist/domain/append-all.mjs +8 -0
  291. package/dist/domain/append-all.mjs.map +1 -0
  292. package/dist/domain/cause-secrets.cjs +37 -0
  293. package/dist/domain/cause-secrets.cjs.map +1 -0
  294. package/dist/domain/cause-secrets.mjs +35 -0
  295. package/dist/domain/cause-secrets.mjs.map +1 -0
  296. package/dist/domain/errors.cjs +9 -3
  297. package/dist/domain/errors.cjs.map +1 -1
  298. package/dist/domain/errors.mjs +9 -3
  299. package/dist/domain/errors.mjs.map +1 -1
  300. package/dist/domain/ir/identifier-text.cjs.map +1 -1
  301. package/dist/domain/ir/identifier-text.mjs +1 -1
  302. package/dist/domain/ir/identifier-text.mjs.map +1 -1
  303. package/dist/domain/ir/ir-shape.cjs +187 -0
  304. package/dist/domain/ir/ir-shape.cjs.map +1 -0
  305. package/dist/domain/ir/ir-shape.mjs +184 -0
  306. package/dist/domain/ir/ir-shape.mjs.map +1 -0
  307. package/dist/domain/ir/newer-identifier-ranges.cjs +182 -29
  308. package/dist/domain/ir/newer-identifier-ranges.cjs.map +1 -1
  309. package/dist/domain/ir/newer-identifier-ranges.mjs +182 -29
  310. package/dist/domain/ir/newer-identifier-ranges.mjs.map +1 -1
  311. package/dist/domain/openapi/alias-expansion.cjs +3 -1
  312. package/dist/domain/openapi/alias-expansion.cjs.map +1 -1
  313. package/dist/domain/openapi/alias-expansion.mjs +3 -1
  314. package/dist/domain/openapi/alias-expansion.mjs.map +1 -1
  315. package/dist/domain/openapi/discriminated-union.cjs +3 -3
  316. package/dist/domain/openapi/discriminated-union.cjs.map +1 -1
  317. package/dist/domain/openapi/discriminated-union.mjs +4 -4
  318. package/dist/domain/openapi/discriminated-union.mjs.map +1 -1
  319. package/dist/domain/openapi/discriminator-conflicts.cjs +10 -6
  320. package/dist/domain/openapi/discriminator-conflicts.cjs.map +1 -1
  321. package/dist/domain/openapi/discriminator-conflicts.mjs +11 -7
  322. package/dist/domain/openapi/discriminator-conflicts.mjs.map +1 -1
  323. package/dist/domain/openapi/json-pointer.cjs +43 -1
  324. package/dist/domain/openapi/json-pointer.cjs.map +1 -1
  325. package/dist/domain/openapi/json-pointer.mjs +40 -2
  326. package/dist/domain/openapi/json-pointer.mjs.map +1 -1
  327. package/dist/domain/openapi/schema-dialect.cjs +15 -0
  328. package/dist/domain/openapi/schema-dialect.cjs.map +1 -0
  329. package/dist/domain/openapi/schema-dialect.mjs +12 -0
  330. package/dist/domain/openapi/schema-dialect.mjs.map +1 -0
  331. package/dist/domain/openapi/unsupported-features.cjs +1 -1
  332. package/dist/domain/openapi/unsupported-features.cjs.map +1 -1
  333. package/dist/domain/openapi/unsupported-features.mjs +2 -2
  334. package/dist/domain/openapi/unsupported-features.mjs.map +1 -1
  335. package/dist/domain/output-fixes.cjs +14 -0
  336. package/dist/domain/output-fixes.cjs.map +1 -1
  337. package/dist/domain/output-fixes.mjs +14 -0
  338. package/dist/domain/output-fixes.mjs.map +1 -1
  339. package/dist/domain/url-secrets.cjs +48 -0
  340. package/dist/domain/url-secrets.cjs.map +1 -0
  341. package/dist/domain/url-secrets.mjs +45 -0
  342. package/dist/domain/url-secrets.mjs.map +1 -0
  343. package/dist/index.d.cts +22 -4
  344. package/dist/index.d.mts +22 -4
  345. package/dist/index.d.ts +22 -4
  346. package/package.json +4 -5
  347. package/src/adapters/node-file-system/file-claims.ts +138 -0
  348. package/src/adapters/node-file-system/file-identity.ts +137 -0
  349. package/src/adapters/node-file-system/node-file-system.ts +145 -18
  350. package/src/adapters/redocly-loader/bundled-mapping-copies.ts +95 -0
  351. package/src/adapters/redocly-loader/bundled-ref-repair.ts +2 -2
  352. package/src/adapters/redocly-loader/loaded-documents.ts +46 -0
  353. package/src/adapters/redocly-loader/redocly-schema-loader.ts +18 -24
  354. package/src/adapters/redocly-loader/ref-access.ts +37 -3
  355. package/src/adapters/redocly-loader/ref-holders.ts +3 -3
  356. package/src/adapters/redocly-loader/remote-body-limit.ts +66 -0
  357. package/src/adapters/redocly-loader/remote-fetch.ts +74 -11
  358. package/src/adapters/redocly-loader/remote-node-limit.ts +62 -0
  359. package/src/adapters/redocly-loader/schema-load-errors.ts +52 -13
  360. package/src/adapters/redocly-loader/schema-source.ts +20 -8
  361. package/src/adapters/redocly-loader/unsupported-keys.ts +3 -7
  362. package/src/adapters/terminal-logger/terminal-logger.ts +10 -5
  363. package/src/api/default-ports.ts +33 -4
  364. package/src/api/discriminator-enum-parent.ts +3 -3
  365. package/src/api/discriminator-other-owners.ts +5 -3
  366. package/src/api/discriminator-patch.ts +5 -1
  367. package/src/api/discriminator-scan.ts +16 -8
  368. package/src/api/document-run.ts +4 -3
  369. package/src/api/legacy-utils.ts +5 -1
  370. package/src/api/openapi-ts.ts +20 -5
  371. package/src/api/run-context.ts +3 -0
  372. package/src/api/schema-callbacks.ts +60 -17
  373. package/src/api/transform-context.ts +7 -3
  374. package/src/api/transform-schema.ts +64 -12
  375. package/src/application/ports/file-system.ts +39 -0
  376. package/src/application/ports/schema-loader.ts +11 -1
  377. package/src/application/print/text-writer.ts +10 -3
  378. package/src/application/stack-overflow.ts +9 -5
  379. package/src/application/transform/document/components.ts +2 -2
  380. package/src/application/transform/document/declared-type-names.ts +36 -0
  381. package/src/application/transform/document/defs-root.ts +2 -2
  382. package/src/application/transform/document/document.ts +4 -3
  383. package/src/application/transform/document/enum-names-given-back.ts +91 -0
  384. package/src/application/transform/document/global-type-references.ts +1 -1
  385. package/src/application/transform/document/media-type.ts +4 -3
  386. package/src/application/transform/document/operation-keys-written.ts +27 -0
  387. package/src/application/transform/document/operation.ts +2 -2
  388. package/src/application/transform/document/parameter-entries.ts +70 -0
  389. package/src/application/transform/document/parameter.ts +21 -9
  390. package/src/application/transform/document/parameters.ts +11 -56
  391. package/src/application/transform/document/path-item.ts +13 -31
  392. package/src/application/transform/document/paths-enum.ts +1 -1
  393. package/src/application/transform/document/paths.ts +6 -2
  394. package/src/application/transform/document/referenced-parameter.ts +146 -0
  395. package/src/application/transform/document/request-body.ts +2 -2
  396. package/src/application/transform/document/response.ts +3 -3
  397. package/src/application/transform/document/responses.ts +2 -2
  398. package/src/application/transform/document/root-type-aliases.ts +17 -38
  399. package/src/application/transform/document/type-resolution.ts +2 -1
  400. package/src/application/transform/document/webhooks.ts +2 -2
  401. package/src/application/transform/footer/document-footer.ts +18 -1
  402. package/src/application/transform/footer/enum-name-claims.ts +62 -0
  403. package/src/application/transform/footer/operation-keys.ts +28 -0
  404. package/src/application/transform/footer/type-evaluation.ts +4 -4
  405. package/src/application/transform/helper-types.ts +6 -5
  406. package/src/application/transform/numbered-names.ts +15 -0
  407. package/src/application/transform/ref-pointer/generated-location.ts +2 -2
  408. package/src/application/transform/ref-type.ts +2 -1
  409. package/src/application/transform/schema/composition-type.ts +2 -2
  410. package/src/application/transform/schema/enum-rule.ts +6 -2
  411. package/src/application/transform/schema/object-type.ts +4 -4
  412. package/src/application/transform/schema/ref-siblings.ts +76 -0
  413. package/src/application/transform/schema/schema-context.ts +3 -0
  414. package/src/application/transform/schema/schema-transform.ts +4 -2
  415. package/src/application/transform/schema-comment.ts +5 -1
  416. package/src/application/transform/transform-context.ts +5 -0
  417. package/src/cli/api-settings.ts +82 -7
  418. package/src/cli/api-writes.ts +117 -0
  419. package/src/cli/cli-dependencies.ts +21 -4
  420. package/src/cli/cli-errors.ts +20 -1
  421. package/src/cli/cli-output.ts +186 -7
  422. package/src/cli/main.ts +6 -3
  423. package/src/cli/redocly-apis.ts +153 -48
  424. package/src/cli/run-cli.ts +37 -8
  425. package/src/domain/append-all.ts +10 -0
  426. package/src/domain/cause-secrets.ts +52 -0
  427. package/src/domain/errors.ts +14 -5
  428. package/src/domain/ir/identifier-text.ts +6 -5
  429. package/src/domain/ir/ir-shape.ts +299 -0
  430. package/src/domain/ir/newer-identifier-ranges.ts +183 -30
  431. package/src/domain/openapi/alias-expansion.ts +4 -1
  432. package/src/domain/openapi/discriminated-union.ts +6 -4
  433. package/src/domain/openapi/discriminator-conflicts.ts +21 -7
  434. package/src/domain/openapi/document-object.ts +2 -0
  435. package/src/domain/openapi/json-pointer.ts +96 -7
  436. package/src/domain/openapi/schema-dialect.ts +22 -0
  437. package/src/domain/openapi/unsupported-features.ts +2 -2
  438. package/src/domain/output-fixes.ts +21 -2
  439. package/src/domain/url-secrets.ts +72 -0
  440. package/src/types/generator-options.ts +1 -1
  441. package/dist/cli/main.d.cts +0 -2
  442. package/dist/cli/main.d.mts +0 -2
  443. package/dist/cli/main.d.ts +0 -2
  444. package/dist/domain/url-credentials.cjs +0 -10
  445. package/dist/domain/url-credentials.cjs.map +0 -1
  446. package/dist/domain/url-credentials.mjs +0 -8
  447. package/dist/domain/url-credentials.mjs.map +0 -1
  448. package/src/domain/url-credentials.ts +0 -18
@@ -8,11 +8,17 @@ import {
8
8
  type Config as RedoclyConfig,
9
9
  } from "@redocly/openapi-core";
10
10
  import type { Logger } from "../../application/ports/logger.js";
11
- import type { SchemaLoader, SchemaLoadRequest } from "../../application/ports/schema-loader.js";
11
+ import type {
12
+ LoadedSchema,
13
+ SchemaLoader,
14
+ SchemaLoadRequest,
15
+ } from "../../application/ports/schema-loader.js";
12
16
  import { withStackOverflowReportedAsync } from "../../application/stack-overflow.js";
13
17
  import { WARNING_CODES, warningText } from "../../domain/warnings.js";
14
18
  import type { OpenAPI3 } from "../../types.js";
19
+ import { removeBundledMappingCopies, schemaKeysOf } from "./bundled-mapping-copies.js";
15
20
  import { repairBundledRefs } from "./bundled-ref-repair.js";
21
+ import { loadedDocuments, localFilesRead } from "./loaded-documents.js";
16
22
  import { findMalformedRefs, malformedRefsError } from "./malformed-refs.js";
17
23
  import { RefAccessResolver } from "./ref-access.js";
18
24
  import { checkRefsResolve } from "./ref-resolution-check.js";
@@ -25,11 +31,7 @@ import {
25
31
  versionUnsupportedError,
26
32
  } from "./schema-load-errors.js";
27
33
  import { parseSchema } from "./schema-source.js";
28
- import {
29
- findUnsupportedKeys,
30
- type LoadedDocument,
31
- unsupportedKeysError,
32
- } from "./unsupported-keys.js";
34
+ import { findUnsupportedKeys, unsupportedKeysError } from "./unsupported-keys.js";
33
35
 
34
36
  /**
35
37
  * The SchemaLoader port on Redocly 1.34 (ADR-005): parse any inherited source type, then lint and
@@ -107,21 +109,6 @@ function sourceFilePath(source: URL): string {
107
109
  }
108
110
  }
109
111
 
110
- /**
111
- * The root document and every other document the resolver loaded, as far as they parsed. A root
112
- * read from a file or URL is in the resolver cache too, and is listed once.
113
- */
114
- async function loadedDocuments(root: Document, resolver: BaseResolver): Promise<LoadedDocument[]> {
115
- const cached = await Promise.all(resolver.cache.values());
116
- const bySource = new Map<string, unknown>([[root.source.absoluteRef, root.parsed]]);
117
- for (const document of cached) {
118
- if ("parsed" in document && !bySource.has(document.source.absoluteRef)) {
119
- bySource.set(document.source.absoluteRef, document.parsed);
120
- }
121
- }
122
- return [...bySource].map(([source, parsed]) => ({ source, parsed }));
123
- }
124
-
125
112
  /** Report the document problems Redocly throws as typed errors; anything else passes through. */
126
113
  async function rethrowDocumentError(
127
114
  error: unknown,
@@ -148,7 +135,7 @@ class RedoclySchemaLoader implements SchemaLoader {
148
135
  this.#options = options;
149
136
  }
150
137
 
151
- async load(request: SchemaLoadRequest): Promise<OpenAPI3> {
138
+ async load(request: SchemaLoadRequest): Promise<LoadedSchema> {
152
139
  const resolver = this.#resolver(request);
153
140
  // Parsing is guarded too: `JSON.stringify` of a deep object source overflows the stack on
154
141
  // Node.js 22 and 24, before linting starts.
@@ -164,7 +151,7 @@ class RedoclySchemaLoader implements SchemaLoader {
164
151
  return bundled;
165
152
  }
166
153
 
167
- async #parseAndBundle(request: SchemaLoadRequest, resolver: BaseResolver): Promise<OpenAPI3> {
154
+ async #parseAndBundle(request: SchemaLoadRequest, resolver: BaseResolver): Promise<LoadedSchema> {
168
155
  const { logger, now } = this.#options;
169
156
  const configLoadedAt = now();
170
157
  logger.debug("Loaded Redoc config", "redoc", now() - configLoadedAt);
@@ -173,7 +160,10 @@ class RedoclySchemaLoader implements SchemaLoader {
173
160
  const document = await parseSchema(request.source, { absoluteRef, resolver });
174
161
  logger.debug("Parsed schema", "redoc", now() - parseStartedAt);
175
162
  assertOpenAPI3(document.parsed);
176
- return this.#lintAndBundle(document, resolver, request.shouldLint);
163
+ const bundled = await this.#lintAndBundle(document, resolver, request.shouldLint);
164
+ const isRootAFile = request.source instanceof URL && request.source.protocol === "file:";
165
+ const localFiles = await localFilesRead(document, resolver, isRootAFile);
166
+ return { document: bundled, localFiles };
177
167
  }
178
168
 
179
169
  #resolver(request: SchemaLoadRequest): RefAccessResolver {
@@ -182,10 +172,12 @@ class RedoclySchemaLoader implements SchemaLoader {
182
172
  timeoutMs: remoteRefTimeoutMs,
183
173
  access: request.refAccess,
184
174
  maxParallelFetches: remoteDocumentLimits.maxParallelFetches,
175
+ bodyLimits: remoteDocumentLimits,
185
176
  });
186
177
  return new RefAccessResolver(config, {
187
178
  access: request.refAccess,
188
179
  maxRemoteDocuments: remoteDocumentLimits.maxDocuments,
180
+ maxRemoteNodes: remoteDocumentLimits.maxNodes,
189
181
  });
190
182
  }
191
183
 
@@ -216,6 +208,7 @@ class RedoclySchemaLoader implements SchemaLoader {
216
208
 
217
209
  async #bundle(document: Document, resolver: BaseResolver): Promise<OpenAPI3> {
218
210
  const startedAt = this.#options.now();
211
+ const schemaKeys = schemaKeysOf(document.parsed);
219
212
  // The lint resolver: documents it already loaded are not fetched again.
220
213
  const bundled = await bundle({
221
214
  config: this.#options.redoc,
@@ -224,6 +217,7 @@ class RedoclySchemaLoader implements SchemaLoader {
224
217
  externalRefResolver: resolver,
225
218
  });
226
219
  this.#report(bundled.problems);
220
+ removeBundledMappingCopies(bundled.bundle.parsed, schemaKeys);
227
221
  repairBundledRefs(bundled.bundle.parsed);
228
222
  this.#options.logger.debug("Bundled schema", "bundle", this.#options.now() - startedAt);
229
223
  return bundled.bundle.parsed;
@@ -10,9 +10,10 @@ import {
10
10
  } from "@redocly/openapi-core";
11
11
  import type { RefAccess } from "../../application/ports/schema-loader.js";
12
12
  import { ERROR_CODES, type OpenAPITypesafeError } from "../../domain/errors.js";
13
- import { redactUrlCredentials } from "../../domain/url-credentials.js";
13
+ import { redactUrlSecrets } from "../../domain/url-secrets.js";
14
14
  import { aliasExpansionFailure } from "./alias-limit.js";
15
15
  import { isHostAllowed } from "./allowed-hosts.js";
16
+ import { isParsedSource, RemoteNodeLimit } from "./remote-node-limit.js";
16
17
  import { refForbiddenError } from "./schema-load-errors.js";
17
18
 
18
19
  /**
@@ -92,15 +93,20 @@ function localFromRemoteRefusal(
92
93
  if (!isRemoteRef(base) || isRemoteRef(absoluteRef)) {
93
94
  return undefined;
94
95
  }
95
- const shownBase = redactUrlCredentials(base);
96
+ const shownBase = redactUrlSecrets(base);
96
97
  return refForbiddenError(absoluteRef, { reason: "local-ref-from-remote", base: shownBase });
97
98
  }
98
99
 
100
+ /** The scheme of an `http(s)` URL, in any case. */
101
+ const REMOTE_SCHEME_RE = /^https?:(?=\/\/)/i;
102
+
99
103
  /** The limits one resolver checks; each load gets its own resolver. */
100
104
  export interface RefAccessLimits {
101
105
  readonly access: RefAccess;
102
106
  /** Remote documents one load may fetch, the root included (threat model T2). */
103
107
  readonly maxRemoteDocuments: number;
108
+ /** Nodes the remote documents of one load may parse into together (threat model T2). */
109
+ readonly maxRemoteNodes: number;
104
110
  }
105
111
 
106
112
  /**
@@ -110,6 +116,7 @@ export interface RefAccessLimits {
110
116
  */
111
117
  export class RefAccessResolver extends BaseResolver {
112
118
  readonly #limits: RefAccessLimits;
119
+ readonly #nodeLimit: RemoteNodeLimit;
113
120
  readonly #refusals: OpenAPITypesafeError[] = [];
114
121
  /** The root document's ref: it is chosen by the developer, so it is not limited. */
115
122
  #rootRef: string | undefined;
@@ -119,6 +126,7 @@ export class RefAccessResolver extends BaseResolver {
119
126
  constructor(config: ResolveConfig, limits: RefAccessLimits) {
120
127
  super(config);
121
128
  this.#limits = limits;
129
+ this.#nodeLimit = new RemoteNodeLimit(limits.maxRemoteNodes);
122
130
  }
123
131
 
124
132
  /** The first document that was refused, if any. */
@@ -126,6 +134,17 @@ export class RefAccessResolver extends BaseResolver {
126
134
  return this.#refusals[0];
127
135
  }
128
136
 
137
+ /**
138
+ * A scheme is the same in any case (RFC 3986, section 3.1), but Redocly takes only `http://` and
139
+ * `https://` as URLs: it read `HTTP://host/x.yaml` as a file path, and its "not found" message
140
+ * showed the user info and the query of the URL, which no longer looked like one. The scheme
141
+ * is written in lowercase first, so the `$ref` loads as the URL it is, within the same limits.
142
+ */
143
+ override resolveExternalRef(base: string | null, ref: string): string {
144
+ const normalized = ref.replace(REMOTE_SCHEME_RE, (scheme) => scheme.toLowerCase());
145
+ return super.resolveExternalRef(base, normalized);
146
+ }
147
+
129
148
  override resolveDocument(
130
149
  base: string | null,
131
150
  ref: string,
@@ -145,7 +164,7 @@ export class RefAccessResolver extends BaseResolver {
145
164
 
146
165
  /** Refuse a YAML alias bomb before Redocly walks it (pre-beta test plan 10.2). */
147
166
  override parseDocument(source: Source, isRoot = false): Document {
148
- const document = super.parseDocument(source, isRoot);
167
+ const document = this.#parsed(source, isRoot);
149
168
  const failure = aliasExpansionFailure(document.parsed);
150
169
  if (failure !== undefined) {
151
170
  throw new YamlParseError(failure, source);
@@ -153,6 +172,21 @@ export class RefAccessResolver extends BaseResolver {
153
172
  return document;
154
173
  }
155
174
 
175
+ /** A remote document is parsed within the node limit; any other one as Redocly parses it. */
176
+ #parsed(source: Source, isRoot: boolean): Document {
177
+ if (!isRemoteRef(source.absoluteRef) || !isParsedSource(source, isRoot)) {
178
+ return super.parseDocument(source, isRoot);
179
+ }
180
+ try {
181
+ return this.#nodeLimit.parse(source);
182
+ } catch (error) {
183
+ if (isRefusal(error)) {
184
+ this.#refusals.push(error);
185
+ }
186
+ throw new YamlParseError(error as Error, source);
187
+ }
188
+ }
189
+
156
190
  override async loadExternalRef(absoluteRef: string): Promise<Source> {
157
191
  const refusal = this.#countRefusal(absoluteRef) ?? (await this.#accessRefusal(absoluteRef));
158
192
  if (refusal !== undefined) {
@@ -1,4 +1,4 @@
1
- import { escapePointerSegment } from "../../domain/openapi/json-pointer.js";
1
+ import { childPointer, ROOT_POINTER } from "../../domain/openapi/json-pointer.js";
2
2
 
3
3
  /** An object with a string `$ref`, and its JSON Pointer in the document. */
4
4
  export interface RefHolder {
@@ -30,13 +30,13 @@ export function findRefHolders(document: unknown): RefHolder[] {
30
30
  pending.push({ value, pointer });
31
31
  }
32
32
  };
33
- enqueue(document, "#");
33
+ enqueue(document, ROOT_POINTER);
34
34
  for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
35
35
  if (hasStringRef(next.value)) {
36
36
  found.push({ node: next.value, pointer: next.pointer });
37
37
  }
38
38
  for (const [key, child] of Object.entries(next.value)) {
39
- enqueue(child, `${next.pointer}/${escapePointerSegment(key)}`);
39
+ enqueue(child, childPointer(next.pointer, key));
40
40
  }
41
41
  }
42
42
  return found;
@@ -0,0 +1,66 @@
1
+ import type { OpenAPITypesafeError } from "../../domain/errors.js";
2
+ import { refForbiddenError } from "./schema-load-errors.js";
3
+
4
+ /** The decoded size limits of the remote documents of one load (threat model T2). */
5
+ export interface RemoteBodyLimits {
6
+ /** Bytes of one remote document's body after `Content-Encoding` is decoded. */
7
+ readonly maxDocumentBytes: number;
8
+ /** Bytes of the bodies of every remote document of one load together, decoded. */
9
+ readonly maxTotalBytes: number;
10
+ }
11
+
12
+ /**
13
+ * Reads remote bodies as a stream and stops at the limits. Fetch decodes `gzip`, `deflate` and
14
+ * `br` before the body reaches the loader, so a body of a few megabytes on the wire can become
15
+ * gigabytes in memory (a "gzip bomb"); counting the decoded bytes while they arrive stops it before
16
+ * the memory is taken. One instance per load: the total covers every document of that load.
17
+ */
18
+ export class RemoteBodyLimit {
19
+ readonly #limits: RemoteBodyLimits;
20
+ #totalBytes = 0;
21
+
22
+ constructor(limits: RemoteBodyLimits) {
23
+ this.#limits = limits;
24
+ }
25
+
26
+ /** The whole body of `response` from `url`; fails with `SCHEMA_REF_FORBIDDEN` past a limit. */
27
+ async read(response: Response, url: string): Promise<ArrayBuffer> {
28
+ const reader = response.body?.getReader();
29
+ if (reader === undefined) {
30
+ return new ArrayBuffer(0);
31
+ }
32
+ const chunks: Uint8Array[] = [];
33
+ let documentBytes = 0;
34
+ for (let next = await reader.read(); !next.done; next = await reader.read()) {
35
+ documentBytes += next.value.byteLength;
36
+ this.#totalBytes += next.value.byteLength;
37
+ const refusal = this.#refusal(url, documentBytes);
38
+ if (refusal !== undefined) {
39
+ await reader.cancel();
40
+ throw refusal;
41
+ }
42
+ chunks.push(next.value);
43
+ }
44
+ return joined(chunks, documentBytes);
45
+ }
46
+
47
+ #refusal(url: string, documentBytes: number): OpenAPITypesafeError | undefined {
48
+ const { maxDocumentBytes, maxTotalBytes } = this.#limits;
49
+ if (documentBytes > maxDocumentBytes) {
50
+ return refForbiddenError(url, { reason: "remote-document-too-large", maxDocumentBytes });
51
+ }
52
+ return this.#totalBytes > maxTotalBytes
53
+ ? refForbiddenError(url, { reason: "remote-documents-too-large", maxTotalBytes })
54
+ : undefined;
55
+ }
56
+ }
57
+
58
+ function joined(chunks: readonly Uint8Array[], byteLength: number): ArrayBuffer {
59
+ const body = new Uint8Array(byteLength);
60
+ let offset = 0;
61
+ for (const chunk of chunks) {
62
+ body.set(chunk, offset);
63
+ offset += chunk.byteLength;
64
+ }
65
+ return body.buffer;
66
+ }
@@ -2,11 +2,12 @@ import type { Config as RedoclyConfig } from "@redocly/openapi-core";
2
2
  import type { RefAccess } from "../../application/ports/schema-loader.js";
3
3
  import { isHostAllowed } from "./allowed-hosts.js";
4
4
  import { type ConfiguredHeader, headersFor } from "./configured-headers.js";
5
+ import { RemoteBodyLimit, type RemoteBodyLimits } from "./remote-body-limit.js";
5
6
  import { refForbiddenError } from "./schema-load-errors.js";
6
7
 
7
8
  /**
8
- * How the loader fetches remote documents: a time limit, the host limit and the configured headers
9
- * checked on every redirect.
9
+ * How the loader fetches remote documents: a time limit, size limits, the host limit and the
10
+ * configured headers checked on every redirect.
10
11
  */
11
12
 
12
13
  type ResolveConfig = RedoclyConfig["resolve"];
@@ -25,9 +26,16 @@ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
25
26
  /** Statuses whose responses have no body; `new Response()` refuses a body for them. */
26
27
  const NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
27
28
 
28
- /** A response whose body is already read, so that reading it again cannot wait or fail. */
29
- async function bufferedResponse(response: Response): Promise<Response> {
30
- const body = await response.arrayBuffer();
29
+ /**
30
+ * A response whose body is already read within the size limits, so that reading it again cannot
31
+ * wait, fail or take more memory.
32
+ */
33
+ async function bufferedResponse(
34
+ response: Response,
35
+ url: string,
36
+ bodyLimit: RemoteBodyLimit,
37
+ ): Promise<Response> {
38
+ const body = await bodyLimit.read(response, url);
31
39
  const { status, statusText, headers } = response;
32
40
  return new Response(NULL_BODY_STATUSES.has(status) ? null : body, {
33
41
  status,
@@ -36,6 +44,45 @@ async function bufferedResponse(response: Response): Promise<Response> {
36
44
  });
37
45
  }
38
46
 
47
+ /**
48
+ * Codes of a connection that closed before any response came. The server can close a kept-alive
49
+ * connection while the process is busy (parsing a large document, generating another API) and
50
+ * has not read the close yet; the next request on that connection then fails with one of these.
51
+ */
52
+ const CLOSED_CONNECTION_CODES: ReadonlySet<string> = new Set([
53
+ "ECONNRESET",
54
+ "EPIPE",
55
+ "UND_ERR_SOCKET",
56
+ ]);
57
+
58
+ /** Methods that may be sent again without a second effect (RFC 9110 §9.2.2). */
59
+ const IDEMPOTENT_METHODS: ReadonlySet<string> = new Set(["GET", "HEAD"]);
60
+ const DEFAULT_METHOD = "GET";
61
+
62
+ /** Whether fetch failed because its connection closed before any response came. */
63
+ function isClosedConnection(error: unknown): boolean {
64
+ const cause = error instanceof TypeError ? error.cause : undefined;
65
+ const code = cause instanceof Error && "code" in cause ? cause.code : undefined;
66
+ return typeof code === "string" && CLOSED_CONNECTION_CODES.has(code);
67
+ }
68
+
69
+ /**
70
+ * Fetch, and send the request once more when its connection closed before any response came, as
71
+ * RFC 9112 §9.3.1 allows for an idempotent request. Fetch drops a connection that failed, so the
72
+ * second attempt goes out on another one; a second failure is the server's answer and is thrown.
73
+ */
74
+ async function fetchOnOpenConnection(input: string, init: RequestInit): Promise<Response> {
75
+ try {
76
+ return await fetch(input, init);
77
+ } catch (error) {
78
+ const method = (init.method ?? DEFAULT_METHOD).toUpperCase();
79
+ if (!(IDEMPOTENT_METHODS.has(method) && isClosedConnection(error))) {
80
+ throw error;
81
+ }
82
+ return fetch(input, init);
83
+ }
84
+ }
85
+
39
86
  /**
40
87
  * Fetch with a time limit for the whole response, body included: Node's own fetch waits five
41
88
  * minutes for a server that never answers, and a server that sends its body one byte at a time
@@ -43,11 +90,13 @@ async function bufferedResponse(response: Response): Promise<Response> {
43
90
  * surfaced as a `DOMException` whose read-only `message` Redocly tried to change, which gave
44
91
  * "Cannot set property message of which has only a getter" instead of the time limit.
45
92
  */
46
- function fetchWithTimeout(timeoutMs: number): RemoteFetch {
93
+ function fetchWithTimeout(timeoutMs: number, bodyLimit: RemoteBodyLimit): RemoteFetch {
47
94
  return async (input, init = {}) => {
48
95
  try {
49
- const response = await fetch(input, { ...init, signal: AbortSignal.timeout(timeoutMs) });
50
- return await bufferedResponse(response);
96
+ // One signal for both attempts, so that a request sent again stays within the same limit.
97
+ const signal = AbortSignal.timeout(timeoutMs);
98
+ const response = await fetchOnOpenConnection(input, { ...init, signal });
99
+ return await bufferedResponse(response, input, bodyLimit);
51
100
  } catch (cause) {
52
101
  const isTimeout = cause instanceof Error && cause.name === "TimeoutError";
53
102
  throw isTimeout ? new Error(`Timed out after ${timeoutMs} ms loading ${input}`) : cause;
@@ -55,6 +104,11 @@ function fetchWithTimeout(timeoutMs: number): RemoteFetch {
55
104
  };
56
105
  }
57
106
 
107
+ /** A configured `customFetch` with its bodies read within the size limits. */
108
+ function fetchWithBodyLimit(fetchImpl: RemoteFetch, bodyLimit: RemoteBodyLimit): RemoteFetch {
109
+ return async (input, init) => bufferedResponse(await fetchImpl(input, init), input, bodyLimit);
110
+ }
111
+
58
112
  /** What every hop of a remote fetch is checked against. */
59
113
  export interface RedirectPolicy {
60
114
  /** `resolve.http.headers` of the Redocly config; chosen again for the URL of every hop. */
@@ -97,11 +151,13 @@ export function fetchWithCheckedRedirects(
97
151
  }
98
152
 
99
153
  /** Bounds on the remote documents of one load (threat model T2, ADR-033). */
100
- export interface RemoteDocumentLimits {
154
+ export interface RemoteDocumentLimits extends RemoteBodyLimits {
101
155
  /** Remote documents one load may fetch, the root included; one more is refused. */
102
156
  readonly maxDocuments: number;
103
157
  /** Remote documents fetched at the same time; the others wait for a free slot. */
104
158
  readonly maxParallelFetches: number;
159
+ /** Nodes (keys and values) the remote documents of one load may parse into together. */
160
+ readonly maxNodes: number;
105
161
  }
106
162
 
107
163
  /**
@@ -135,11 +191,13 @@ export interface RemoteFetchOptions {
135
191
  readonly timeoutMs: number;
136
192
  readonly access: RefAccess;
137
193
  readonly maxParallelFetches: number;
194
+ readonly bodyLimits: RemoteBodyLimits;
138
195
  }
139
196
 
140
197
  /**
141
198
  * The resolve config with the fetch the loader uses. A `customFetch` of the Redocly config is
142
- * kept and wrapped, so that each redirect is checked and only a few documents load at once. The
199
+ * kept and wrapped, so that each redirect is checked, each body is read within the size limits
200
+ * and only a few documents load at once. The
143
201
  * configured headers move into that wrapper: Redocly would match them against the raw URL and
144
202
  * send them on through redirects.
145
203
  */
@@ -149,7 +207,12 @@ export function resolveConfigWithAccess(
149
207
  ): ResolveConfig {
150
208
  // Redocly types `customFetch` as `Function`; it is called as fetch is.
151
209
  const configured = resolve.http.customFetch as RemoteFetch | undefined;
152
- const base = configured ?? fetchWithTimeout(options.timeoutMs);
210
+ // One limit per load, so that the total covers every document this resolver fetches.
211
+ const bodyLimit = new RemoteBodyLimit(options.bodyLimits);
212
+ const base =
213
+ configured === undefined
214
+ ? fetchWithTimeout(options.timeoutMs, bodyLimit)
215
+ : fetchWithBodyLimit(configured, bodyLimit);
153
216
  const checked = fetchWithCheckedRedirects(base, {
154
217
  headers: resolve.http.headers,
155
218
  hosts: options.access.remoteHosts,
@@ -0,0 +1,62 @@
1
+ import { type Document, parseYaml, type Source } from "@redocly/openapi-core";
2
+ import type { OpenAPITypesafeError } from "../../domain/errors.js";
3
+ import { refForbiddenError } from "./schema-load-errors.js";
4
+
5
+ /**
6
+ * Bounds the parsed size of the remote documents of one load (threat model T2). The byte limits
7
+ * do not bound memory: parsing multiplies it by the density of the text, and 250 MiB of `{},`
8
+ * (87 million empty objects, under a quarter of a megabyte as gzip) took the process past its
9
+ * 4 GB heap. The parser reports every node it builds, keys included, and the parse stops when
10
+ * the nodes of all remote documents of the load pass the limit, before their memory is taken.
11
+ */
12
+
13
+ /** The extensions that Redocly 1.34's `BaseResolver.parseDocument` parses as YAML or JSON. */
14
+ const PARSED_EXTENSIONS: ReadonlySet<string> = new Set([".json", ".yml", ".yaml"]);
15
+ const PARSED_MIME_TYPE = /(json|yaml|openapi)/;
16
+
17
+ /** Whether Redocly parses this source, or keeps its text: the same rule as its own resolver. */
18
+ export function isParsedSource(source: Source, isRoot: boolean): boolean {
19
+ const { absoluteRef, mimeType } = source;
20
+ const extension = absoluteRef.slice(absoluteRef.lastIndexOf("."));
21
+ return isRoot || PARSED_EXTENSIONS.has(extension) || PARSED_MIME_TYPE.test(mimeType ?? "");
22
+ }
23
+
24
+ /** The parser's event for a node it starts to build. */
25
+ const NODE_OPENED = "open";
26
+
27
+ /** One instance per load: the count covers every remote document of that load. */
28
+ export class RemoteNodeLimit {
29
+ readonly #maxNodes: number;
30
+ #nodes = 0;
31
+
32
+ constructor(maxNodes: number) {
33
+ this.#maxNodes = maxNodes;
34
+ }
35
+
36
+ /**
37
+ * The parsed document of `source`, as Redocly parses it. Fails with `SCHEMA_REF_FORBIDDEN`
38
+ * (`remote-documents-too-many-nodes`) once the nodes of the load pass the limit.
39
+ */
40
+ parse(source: Source): Document {
41
+ const listener = (event: string): void => {
42
+ if (event !== NODE_OPENED) {
43
+ return;
44
+ }
45
+ this.#nodes += 1;
46
+ const refusal = this.#refusal(source.absoluteRef);
47
+ if (refusal !== undefined) {
48
+ throw refusal;
49
+ }
50
+ };
51
+ return { source, parsed: parseYaml(source.body, { filename: source.absoluteRef, listener }) };
52
+ }
53
+
54
+ #refusal(url: string): OpenAPITypesafeError | undefined {
55
+ return this.#nodes > this.#maxNodes
56
+ ? refForbiddenError(url, {
57
+ reason: "remote-documents-too-many-nodes",
58
+ maxNodes: this.#maxNodes,
59
+ })
60
+ : undefined;
61
+ }
62
+ }
@@ -1,6 +1,6 @@
1
1
  import { assertNever } from "../../domain/assert-never.js";
2
2
  import { ERROR_CODES, OpenAPITypesafeError } from "../../domain/errors.js";
3
- import { redactUrlCredentials } from "../../domain/url-credentials.js";
3
+ import { redactUnparsedUrl, redactUrlSecrets } from "../../domain/url-secrets.js";
4
4
 
5
5
  /**
6
6
  * The typed errors of loading, validating and bundling a document (ADR-011). Messages are the
@@ -23,6 +23,15 @@ export function sourceInvalidError(source: unknown): OpenAPITypesafeError {
23
23
  });
24
24
  }
25
25
 
26
+ /** A source string starts with a URL scheme but does not parse as a URL. */
27
+ export function urlInvalidError(text: string): OpenAPITypesafeError {
28
+ return new OpenAPITypesafeError({
29
+ code: ERROR_CODES.SCHEMA_SOURCE_INVALID,
30
+ message: `The string ${redactUnparsedUrl(text)} starts like a URL but is not a valid URL`,
31
+ details: { reason: "url", received: "string" },
32
+ });
33
+ }
34
+
26
35
  function causeMessage(cause: unknown): string {
27
36
  return cause instanceof Error ? cause.message : String(cause);
28
37
  }
@@ -30,19 +39,29 @@ function causeMessage(cause: unknown): string {
30
39
  /** Enough of a source string to recognise it in the message. */
31
40
  const SOURCE_TEXT_IN_MESSAGE = 200;
32
41
 
42
+ /** Where document text that is only a YAML string came from. */
43
+ export type TextOrigin = "string" | "stream";
44
+
45
+ const LOAD_FILE_ADVICE =
46
+ "To load a file, pass a URL: openapiTS(pathToFileURL(path)) from node:url.";
47
+
33
48
  /**
34
49
  * A string source is document text, so a file path parses as a YAML string scalar, not as a
35
- * document (docs/node.md). The message says how to load the file instead.
50
+ * document (docs/node.md). The message says how to load the file instead. Text read from a
51
+ * stream (stdin in the CLI) is named as such; `details.text` lets the CLI give its own advice.
36
52
  */
37
- export function pathStringError(text: string): OpenAPITypesafeError {
53
+ export function pathStringError(text: string, origin: TextOrigin): OpenAPITypesafeError {
38
54
  const shown =
39
55
  text.length > SOURCE_TEXT_IN_MESSAGE ? `${text.slice(0, SOURCE_TEXT_IN_MESSAGE)}…` : text;
56
+ const message =
57
+ origin === "string"
58
+ ? `The string "${shown}" was parsed as YAML text, not loaded as a file. ${LOAD_FILE_ADVICE}`
59
+ : `The stream held only the YAML string "${shown}", not an OpenAPI document. ` +
60
+ LOAD_FILE_ADVICE;
40
61
  return new OpenAPITypesafeError({
41
62
  code: ERROR_CODES.SCHEMA_SOURCE_INVALID,
42
- message:
43
- `The string "${shown}" was parsed as YAML text, not loaded as a file. ` +
44
- "To load a file, pass a URL: openapiTS(pathToFileURL(path)) from node:url.",
45
- details: { reason: "path", received: "string" },
63
+ message,
64
+ details: { reason: "path", received: origin, text: shown },
46
65
  });
47
66
  }
48
67
 
@@ -51,7 +70,7 @@ export function loadFailedError(absoluteRef: string, cause: unknown): OpenAPITyp
51
70
  return new OpenAPITypesafeError({
52
71
  code: ERROR_CODES.SCHEMA_LOAD_FAILED,
53
72
  message: causeMessage(cause),
54
- details: { source: redactUrlCredentials(absoluteRef) },
73
+ details: { source: redactUrlSecrets(absoluteRef) },
55
74
  cause,
56
75
  });
57
76
  }
@@ -65,7 +84,7 @@ export function parseFailedError(
65
84
  return new OpenAPITypesafeError({
66
85
  code: ERROR_CODES.SCHEMA_PARSE_FAILED,
67
86
  message: causeMessage(cause),
68
- details: { format, source: redactUrlCredentials(absoluteRef) },
87
+ details: { format, source: redactUrlSecrets(absoluteRef) },
69
88
  cause,
70
89
  });
71
90
  }
@@ -90,7 +109,7 @@ export function schemaInvalidError(problems: readonly string[]): OpenAPITypesafe
90
109
  return new OpenAPITypesafeError({
91
110
  code: ERROR_CODES.SCHEMA_INVALID,
92
111
  message: problems.at(-1) ?? "",
93
- details: { problems: problems.map(redactUrlCredentials) },
112
+ details: { problems: problems.map(redactUrlSecrets) },
94
113
  });
95
114
  }
96
115
 
@@ -113,7 +132,17 @@ export type RefRefusalDetails =
113
132
  | { readonly reason: "outside-local-root"; readonly localRoot: string }
114
133
  | { readonly reason: "host-not-allowed"; readonly allowedHosts: readonly string[] }
115
134
  | { readonly reason: "local-ref-from-remote"; readonly base: string }
116
- | { readonly reason: "too-many-remote-documents"; readonly maxDocuments: number };
135
+ | { readonly reason: "too-many-remote-documents"; readonly maxDocuments: number }
136
+ | { readonly reason: "remote-document-too-large"; readonly maxDocumentBytes: number }
137
+ | { readonly reason: "remote-documents-too-large"; readonly maxTotalBytes: number }
138
+ | { readonly reason: "remote-documents-too-many-nodes"; readonly maxNodes: number };
139
+
140
+ const BYTES_PER_MEBIBYTE = 1024 * 1024;
141
+
142
+ /** A byte count in MiB, as the limits are chosen: `256 MiB`; a smaller limit in bytes. */
143
+ function sizeText(bytes: number): string {
144
+ return bytes % BYTES_PER_MEBIBYTE === 0 ? `${bytes / BYTES_PER_MEBIBYTE} MiB` : `${bytes} bytes`;
145
+ }
117
146
 
118
147
  function limitText(details: RefRefusalDetails): string {
119
148
  switch (details.reason) {
@@ -127,6 +156,16 @@ function limitText(details: RefRefusalDetails): string {
127
156
  return `it is not an http(s) URL, and the $ref is in the remote document ${details.base}`;
128
157
  case "too-many-remote-documents":
129
158
  return `the document refers to more than ${details.maxDocuments} remote documents`;
159
+ case "remote-document-too-large":
160
+ return `its body is larger than ${sizeText(details.maxDocumentBytes)} once decoded`;
161
+ case "remote-documents-too-large": {
162
+ const limit = sizeText(details.maxTotalBytes);
163
+ return `the remote documents of this run are larger than ${limit} together once decoded`;
164
+ }
165
+ case "remote-documents-too-many-nodes": {
166
+ const limit = details.maxNodes;
167
+ return `the remote documents of this run parse into more than ${limit} nodes together`;
168
+ }
130
169
  default:
131
170
  return assertNever(details);
132
171
  }
@@ -134,13 +173,13 @@ function limitText(details: RefRefusalDetails): string {
134
173
 
135
174
  /**
136
175
  * A `$ref` is outside the local root, its host is not allowed (threat model R1, R2), a remote
137
- * document refers to a local file (T3), or there are too many remote documents (T2).
176
+ * document refers to a local file (T3), or there are too many or too large remote documents (T2).
138
177
  */
139
178
  export function refForbiddenError(ref: string, details: RefRefusalDetails): OpenAPITypesafeError {
140
179
  const limit = limitText(details);
141
180
  return new OpenAPITypesafeError({
142
181
  code: ERROR_CODES.SCHEMA_REF_FORBIDDEN,
143
182
  message: `Refused to load ${ref}: ${limit}`,
144
- details: { ref: redactUrlCredentials(ref), ...details },
183
+ details: { ref: redactUrlSecrets(ref), ...details },
145
184
  });
146
185
  }