@fgv/ts-extras 5.1.0-5 → 5.1.0-50

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 (488) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +90 -0
  3. package/dist/index.browser.js +6 -2
  4. package/dist/index.browser.js.map +1 -0
  5. package/dist/index.js +2 -1
  6. package/dist/index.js.map +1 -0
  7. package/dist/packlets/ai-assist/chatRequestBuilders.js +282 -0
  8. package/dist/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
  9. package/dist/packlets/ai-assist/{apiClient.js → completionClient.js} +165 -214
  10. package/dist/packlets/ai-assist/completionClient.js.map +1 -0
  11. package/dist/packlets/ai-assist/converters.js +47 -3
  12. package/dist/packlets/ai-assist/converters.js.map +1 -0
  13. package/dist/packlets/ai-assist/embeddingClient.js +345 -0
  14. package/dist/packlets/ai-assist/embeddingClient.js.map +1 -0
  15. package/dist/packlets/ai-assist/endpoint.js +107 -0
  16. package/dist/packlets/ai-assist/endpoint.js.map +1 -0
  17. package/dist/packlets/ai-assist/http.js +104 -0
  18. package/dist/packlets/ai-assist/http.js.map +1 -0
  19. package/dist/packlets/ai-assist/imageGenerationClient.js +454 -0
  20. package/dist/packlets/ai-assist/imageGenerationClient.js.map +1 -0
  21. package/dist/packlets/ai-assist/imageOptionsResolver.js +193 -0
  22. package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  23. package/dist/packlets/ai-assist/index.js +12 -4
  24. package/dist/packlets/ai-assist/index.js.map +1 -0
  25. package/dist/packlets/ai-assist/jsonCompletion.js +93 -0
  26. package/dist/packlets/ai-assist/jsonCompletion.js.map +1 -0
  27. package/dist/packlets/ai-assist/jsonResponse.js +423 -0
  28. package/dist/packlets/ai-assist/jsonResponse.js.map +1 -0
  29. package/dist/packlets/ai-assist/listModelsClient.js +294 -0
  30. package/dist/packlets/ai-assist/listModelsClient.js.map +1 -0
  31. package/dist/packlets/ai-assist/model.js +301 -9
  32. package/dist/packlets/ai-assist/model.js.map +1 -0
  33. package/dist/packlets/ai-assist/registry.js +376 -12
  34. package/dist/packlets/ai-assist/registry.js.map +1 -0
  35. package/dist/packlets/ai-assist/sseParser.js +123 -0
  36. package/dist/packlets/ai-assist/sseParser.js.map +1 -0
  37. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +376 -0
  38. package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
  39. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +687 -0
  40. package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  41. package/dist/packlets/ai-assist/streamingAdapters/common.js +187 -0
  42. package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
  43. package/dist/packlets/ai-assist/streamingAdapters/gemini.js +208 -0
  44. package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
  45. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +175 -0
  46. package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
  47. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +390 -0
  48. package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
  49. package/dist/packlets/ai-assist/streamingAdapters/proxy.js +178 -0
  50. package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
  51. package/dist/packlets/ai-assist/streamingClient.js +140 -0
  52. package/dist/packlets/ai-assist/streamingClient.js.map +1 -0
  53. package/dist/packlets/ai-assist/thinkingOptionsResolver.js +288 -0
  54. package/dist/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  55. package/dist/packlets/ai-assist/toolFormats.js +106 -10
  56. package/dist/packlets/ai-assist/toolFormats.js.map +1 -0
  57. package/dist/packlets/conversion/converters.js +35 -1
  58. package/dist/packlets/conversion/converters.js.map +1 -0
  59. package/dist/packlets/conversion/index.js.map +1 -0
  60. package/dist/packlets/crypto-utils/constants.js.map +1 -0
  61. package/dist/packlets/crypto-utils/converters.js +42 -4
  62. package/dist/packlets/crypto-utils/converters.js.map +1 -0
  63. package/dist/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
  64. package/dist/packlets/crypto-utils/encryptedFile.js +37 -0
  65. package/dist/packlets/crypto-utils/encryptedFile.js.map +1 -0
  66. package/dist/packlets/crypto-utils/hpkeProvider.js +355 -0
  67. package/dist/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  68. package/dist/packlets/crypto-utils/index.browser.js +13 -2
  69. package/dist/packlets/crypto-utils/index.browser.js.map +1 -0
  70. package/dist/packlets/crypto-utils/index.js +9 -1
  71. package/dist/packlets/crypto-utils/index.js.map +1 -0
  72. package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js +71 -0
  73. package/dist/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
  74. package/dist/packlets/crypto-utils/keystore/converters.js +111 -16
  75. package/dist/packlets/crypto-utils/keystore/converters.js.map +1 -0
  76. package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +287 -0
  77. package/dist/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
  78. package/dist/packlets/crypto-utils/keystore/index.browser.js +36 -0
  79. package/dist/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
  80. package/dist/packlets/crypto-utils/keystore/index.js +3 -0
  81. package/dist/packlets/crypto-utils/keystore/index.js.map +1 -0
  82. package/dist/packlets/crypto-utils/keystore/keyStore.js +872 -119
  83. package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
  84. package/dist/packlets/crypto-utils/keystore/model.js +35 -4
  85. package/dist/packlets/crypto-utils/keystore/model.js.map +1 -0
  86. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js +21 -0
  87. package/dist/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
  88. package/dist/packlets/crypto-utils/model.js +32 -0
  89. package/dist/packlets/crypto-utils/model.js.map +1 -0
  90. package/dist/packlets/crypto-utils/nodeCryptoProvider.js +347 -1
  91. package/dist/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
  92. package/dist/packlets/crypto-utils/seedDerivedKeyPair.js +120 -0
  93. package/dist/packlets/crypto-utils/seedDerivedKeyPair.js.map +1 -0
  94. package/dist/packlets/crypto-utils/spkiHelpers.js +284 -0
  95. package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -0
  96. package/dist/packlets/csv/csvFileHelpers.js +0 -14
  97. package/dist/packlets/csv/csvFileHelpers.js.map +1 -0
  98. package/dist/packlets/csv/csvHelpers.js +14 -0
  99. package/dist/packlets/csv/csvHelpers.js.map +1 -0
  100. package/dist/packlets/csv/index.browser.js +1 -3
  101. package/dist/packlets/csv/index.browser.js.map +1 -0
  102. package/dist/packlets/csv/index.js.map +1 -0
  103. package/dist/packlets/experimental/extendedArray.js.map +1 -0
  104. package/dist/packlets/experimental/formatter.js.map +1 -0
  105. package/dist/packlets/experimental/index.js.map +1 -0
  106. package/dist/packlets/experimental/rangeOf.js.map +1 -0
  107. package/dist/packlets/hash/index.browser.js.map +1 -0
  108. package/dist/packlets/hash/index.js.map +1 -0
  109. package/dist/packlets/hash/index.node.js.map +1 -0
  110. package/dist/packlets/hash/md5Normalizer.browser.js.map +1 -0
  111. package/dist/packlets/hash/md5Normalizer.js.map +1 -0
  112. package/dist/packlets/mustache/index.js.map +1 -0
  113. package/dist/packlets/mustache/interfaces.js.map +1 -0
  114. package/dist/packlets/mustache/mustacheTemplate.js +42 -4
  115. package/dist/packlets/mustache/mustacheTemplate.js.map +1 -0
  116. package/dist/packlets/record-jar/index.browser.js +1 -3
  117. package/dist/packlets/record-jar/index.browser.js.map +1 -0
  118. package/dist/packlets/record-jar/index.js.map +1 -0
  119. package/dist/packlets/record-jar/recordJarFileHelpers.js +0 -18
  120. package/dist/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
  121. package/dist/packlets/record-jar/recordJarHelpers.js +18 -0
  122. package/dist/packlets/record-jar/recordJarHelpers.js.map +1 -0
  123. package/dist/packlets/safer-fetch/addressClassification.js +438 -0
  124. package/dist/packlets/safer-fetch/addressClassification.js.map +1 -0
  125. package/dist/packlets/safer-fetch/addressPolicy.js +119 -0
  126. package/dist/packlets/safer-fetch/addressPolicy.js.map +1 -0
  127. package/dist/packlets/safer-fetch/contentType.js +120 -0
  128. package/dist/packlets/safer-fetch/contentType.js.map +1 -0
  129. package/dist/packlets/safer-fetch/deadline.js +254 -0
  130. package/dist/packlets/safer-fetch/deadline.js.map +1 -0
  131. package/dist/packlets/safer-fetch/defaults.js +131 -0
  132. package/dist/packlets/safer-fetch/defaults.js.map +1 -0
  133. package/dist/packlets/safer-fetch/failureReason.js +21 -0
  134. package/dist/packlets/safer-fetch/failureReason.js.map +1 -0
  135. package/dist/packlets/safer-fetch/guards.js +148 -0
  136. package/dist/packlets/safer-fetch/guards.js.map +1 -0
  137. package/dist/packlets/safer-fetch/index.browser.js +58 -0
  138. package/dist/packlets/safer-fetch/index.browser.js.map +1 -0
  139. package/dist/packlets/safer-fetch/index.js +52 -0
  140. package/dist/packlets/safer-fetch/index.js.map +1 -0
  141. package/dist/packlets/safer-fetch/model.js +21 -0
  142. package/dist/packlets/safer-fetch/model.js.map +1 -0
  143. package/dist/packlets/safer-fetch/nodeAddressGuard.js +191 -0
  144. package/dist/packlets/safer-fetch/nodeAddressGuard.js.map +1 -0
  145. package/dist/packlets/safer-fetch/redirect.js +93 -0
  146. package/dist/packlets/safer-fetch/redirect.js.map +1 -0
  147. package/dist/packlets/safer-fetch/retry.js +188 -0
  148. package/dist/packlets/safer-fetch/retry.js.map +1 -0
  149. package/dist/packlets/safer-fetch/saferFetch.js +755 -0
  150. package/dist/packlets/safer-fetch/saferFetch.js.map +1 -0
  151. package/dist/packlets/safer-fetch/transport.js +50 -0
  152. package/dist/packlets/safer-fetch/transport.js.map +1 -0
  153. package/dist/packlets/yaml/converters.js.map +1 -0
  154. package/dist/packlets/yaml/index.js +1 -0
  155. package/dist/packlets/yaml/index.js.map +1 -0
  156. package/dist/packlets/yaml/serializers.js +48 -0
  157. package/dist/packlets/yaml/serializers.js.map +1 -0
  158. package/dist/packlets/zip-file-tree/index.js.map +1 -0
  159. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js +99 -9
  160. package/dist/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
  161. package/dist/packlets/zip-file-tree/zipFileTreeWriter.js +16 -1
  162. package/dist/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
  163. package/dist/ts-extras.d.ts +6648 -553
  164. package/dist/tsdoc-metadata.json +1 -1
  165. package/lib/index.browser.d.ts +5 -2
  166. package/lib/index.browser.d.ts.map +1 -0
  167. package/lib/index.browser.js +11 -3
  168. package/lib/index.browser.js.map +1 -0
  169. package/lib/index.d.ts +2 -1
  170. package/lib/index.d.ts.map +1 -0
  171. package/lib/index.js +3 -1
  172. package/lib/index.js.map +1 -0
  173. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts +149 -0
  174. package/lib/packlets/ai-assist/chatRequestBuilders.d.ts.map +1 -0
  175. package/lib/packlets/ai-assist/chatRequestBuilders.js +293 -0
  176. package/lib/packlets/ai-assist/chatRequestBuilders.js.map +1 -0
  177. package/lib/packlets/ai-assist/completionClient.d.ts +74 -0
  178. package/lib/packlets/ai-assist/completionClient.d.ts.map +1 -0
  179. package/lib/packlets/ai-assist/{apiClient.js → completionClient.js} +164 -213
  180. package/lib/packlets/ai-assist/completionClient.js.map +1 -0
  181. package/lib/packlets/ai-assist/converters.d.ts +15 -1
  182. package/lib/packlets/ai-assist/converters.d.ts.map +1 -0
  183. package/lib/packlets/ai-assist/converters.js +47 -3
  184. package/lib/packlets/ai-assist/converters.js.map +1 -0
  185. package/lib/packlets/ai-assist/embeddingClient.d.ts +69 -0
  186. package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -0
  187. package/lib/packlets/ai-assist/embeddingClient.js +349 -0
  188. package/lib/packlets/ai-assist/embeddingClient.js.map +1 -0
  189. package/lib/packlets/ai-assist/endpoint.d.ts +49 -0
  190. package/lib/packlets/ai-assist/endpoint.d.ts.map +1 -0
  191. package/lib/packlets/ai-assist/endpoint.js +113 -0
  192. package/lib/packlets/ai-assist/endpoint.js.map +1 -0
  193. package/lib/packlets/ai-assist/http.d.ts +34 -0
  194. package/lib/packlets/ai-assist/http.d.ts.map +1 -0
  195. package/lib/packlets/ai-assist/http.js +109 -0
  196. package/lib/packlets/ai-assist/http.js.map +1 -0
  197. package/lib/packlets/ai-assist/imageGenerationClient.d.ts +46 -0
  198. package/lib/packlets/ai-assist/imageGenerationClient.d.ts.map +1 -0
  199. package/lib/packlets/ai-assist/imageGenerationClient.js +458 -0
  200. package/lib/packlets/ai-assist/imageGenerationClient.js.map +1 -0
  201. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts +66 -0
  202. package/lib/packlets/ai-assist/imageOptionsResolver.d.ts.map +1 -0
  203. package/lib/packlets/ai-assist/imageOptionsResolver.js +197 -0
  204. package/lib/packlets/ai-assist/imageOptionsResolver.js.map +1 -0
  205. package/lib/packlets/ai-assist/index.d.ts +12 -4
  206. package/lib/packlets/ai-assist/index.d.ts.map +1 -0
  207. package/lib/packlets/ai-assist/index.js +47 -4
  208. package/lib/packlets/ai-assist/index.js.map +1 -0
  209. package/lib/packlets/ai-assist/jsonCompletion.d.ts +93 -0
  210. package/lib/packlets/ai-assist/jsonCompletion.d.ts.map +1 -0
  211. package/lib/packlets/ai-assist/jsonCompletion.js +97 -0
  212. package/lib/packlets/ai-assist/jsonCompletion.js.map +1 -0
  213. package/lib/packlets/ai-assist/jsonResponse.d.ts +194 -0
  214. package/lib/packlets/ai-assist/jsonResponse.d.ts.map +1 -0
  215. package/lib/packlets/ai-assist/jsonResponse.js +429 -0
  216. package/lib/packlets/ai-assist/jsonResponse.js.map +1 -0
  217. package/lib/packlets/ai-assist/listModelsClient.d.ts +46 -0
  218. package/lib/packlets/ai-assist/listModelsClient.d.ts.map +1 -0
  219. package/lib/packlets/ai-assist/listModelsClient.js +298 -0
  220. package/lib/packlets/ai-assist/listModelsClient.js.map +1 -0
  221. package/lib/packlets/ai-assist/model.d.ts +1346 -12
  222. package/lib/packlets/ai-assist/model.d.ts.map +1 -0
  223. package/lib/packlets/ai-assist/model.js +309 -10
  224. package/lib/packlets/ai-assist/model.js.map +1 -0
  225. package/lib/packlets/ai-assist/registry.d.ts +76 -1
  226. package/lib/packlets/ai-assist/registry.d.ts.map +1 -0
  227. package/lib/packlets/ai-assist/registry.js +381 -13
  228. package/lib/packlets/ai-assist/registry.js.map +1 -0
  229. package/lib/packlets/ai-assist/sseParser.d.ts +45 -0
  230. package/lib/packlets/ai-assist/sseParser.d.ts.map +1 -0
  231. package/lib/packlets/ai-assist/sseParser.js +128 -0
  232. package/lib/packlets/ai-assist/sseParser.js.map +1 -0
  233. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +72 -0
  234. package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -0
  235. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +379 -0
  236. package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -0
  237. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +269 -0
  238. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -0
  239. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +694 -0
  240. package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -0
  241. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +163 -0
  242. package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -0
  243. package/lib/packlets/ai-assist/streamingAdapters/common.js +193 -0
  244. package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -0
  245. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +43 -0
  246. package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -0
  247. package/lib/packlets/ai-assist/streamingAdapters/gemini.js +211 -0
  248. package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -0
  249. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +19 -0
  250. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -0
  251. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +178 -0
  252. package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -0
  253. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +33 -0
  254. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -0
  255. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +393 -0
  256. package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -0
  257. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts +34 -0
  258. package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -0
  259. package/lib/packlets/ai-assist/streamingAdapters/proxy.js +181 -0
  260. package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -0
  261. package/lib/packlets/ai-assist/streamingClient.d.ts +50 -0
  262. package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -0
  263. package/lib/packlets/ai-assist/streamingClient.js +146 -0
  264. package/lib/packlets/ai-assist/streamingClient.js.map +1 -0
  265. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts +87 -0
  266. package/lib/packlets/ai-assist/thinkingOptionsResolver.d.ts.map +1 -0
  267. package/lib/packlets/ai-assist/thinkingOptionsResolver.js +294 -0
  268. package/lib/packlets/ai-assist/thinkingOptionsResolver.js.map +1 -0
  269. package/lib/packlets/ai-assist/toolFormats.d.ts +40 -9
  270. package/lib/packlets/ai-assist/toolFormats.d.ts.map +1 -0
  271. package/lib/packlets/ai-assist/toolFormats.js +107 -10
  272. package/lib/packlets/ai-assist/toolFormats.js.map +1 -0
  273. package/lib/packlets/conversion/converters.d.ts +8 -1
  274. package/lib/packlets/conversion/converters.d.ts.map +1 -0
  275. package/lib/packlets/conversion/converters.js +36 -2
  276. package/lib/packlets/conversion/converters.js.map +1 -0
  277. package/lib/packlets/conversion/index.d.ts.map +1 -0
  278. package/lib/packlets/conversion/index.js.map +1 -0
  279. package/lib/packlets/crypto-utils/constants.d.ts.map +1 -0
  280. package/lib/packlets/crypto-utils/constants.js.map +1 -0
  281. package/lib/packlets/crypto-utils/converters.d.ts +20 -1
  282. package/lib/packlets/crypto-utils/converters.d.ts.map +1 -0
  283. package/lib/packlets/crypto-utils/converters.js +43 -5
  284. package/lib/packlets/crypto-utils/converters.js.map +1 -0
  285. package/lib/packlets/crypto-utils/directEncryptionProvider.d.ts.map +1 -0
  286. package/lib/packlets/crypto-utils/directEncryptionProvider.js.map +1 -0
  287. package/lib/packlets/crypto-utils/encryptedFile.d.ts +25 -0
  288. package/lib/packlets/crypto-utils/encryptedFile.d.ts.map +1 -0
  289. package/lib/packlets/crypto-utils/encryptedFile.js +38 -0
  290. package/lib/packlets/crypto-utils/encryptedFile.js.map +1 -0
  291. package/lib/packlets/crypto-utils/hpkeProvider.d.ts +150 -0
  292. package/lib/packlets/crypto-utils/hpkeProvider.d.ts.map +1 -0
  293. package/lib/packlets/crypto-utils/hpkeProvider.js +359 -0
  294. package/lib/packlets/crypto-utils/hpkeProvider.js.map +1 -0
  295. package/lib/packlets/crypto-utils/index.browser.d.ts +5 -1
  296. package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -0
  297. package/lib/packlets/crypto-utils/index.browser.js +28 -3
  298. package/lib/packlets/crypto-utils/index.browser.js.map +1 -0
  299. package/lib/packlets/crypto-utils/index.d.ts +5 -1
  300. package/lib/packlets/crypto-utils/index.d.ts.map +1 -0
  301. package/lib/packlets/crypto-utils/index.js +24 -1
  302. package/lib/packlets/crypto-utils/index.js.map +1 -0
  303. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts +54 -0
  304. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.d.ts.map +1 -0
  305. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js +74 -0
  306. package/lib/packlets/crypto-utils/keyPairAlgorithmParams.js.map +1 -0
  307. package/lib/packlets/crypto-utils/keystore/converters.d.ts +68 -6
  308. package/lib/packlets/crypto-utils/keystore/converters.d.ts.map +1 -0
  309. package/lib/packlets/crypto-utils/keystore/converters.js +109 -14
  310. package/lib/packlets/crypto-utils/keystore/converters.js.map +1 -0
  311. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts +148 -0
  312. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.d.ts.map +1 -0
  313. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js +324 -0
  314. package/lib/packlets/crypto-utils/keystore/encryptedFilePrivateKeyStorage.js.map +1 -0
  315. package/lib/packlets/crypto-utils/keystore/index.browser.d.ts +10 -0
  316. package/lib/packlets/crypto-utils/keystore/index.browser.d.ts.map +1 -0
  317. package/lib/packlets/crypto-utils/keystore/index.browser.js +76 -0
  318. package/lib/packlets/crypto-utils/keystore/index.browser.js.map +1 -0
  319. package/lib/packlets/crypto-utils/keystore/index.d.ts +2 -0
  320. package/lib/packlets/crypto-utils/keystore/index.d.ts.map +1 -0
  321. package/lib/packlets/crypto-utils/keystore/index.js +5 -1
  322. package/lib/packlets/crypto-utils/keystore/index.js.map +1 -0
  323. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +314 -13
  324. package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -0
  325. package/lib/packlets/crypto-utils/keystore/keyStore.js +877 -124
  326. package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -0
  327. package/lib/packlets/crypto-utils/keystore/model.d.ts +404 -21
  328. package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -0
  329. package/lib/packlets/crypto-utils/keystore/model.js +37 -5
  330. package/lib/packlets/crypto-utils/keystore/model.js.map +1 -0
  331. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts +53 -0
  332. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.d.ts.map +1 -0
  333. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js +22 -0
  334. package/lib/packlets/crypto-utils/keystore/privateKeyStorage.js.map +1 -0
  335. package/lib/packlets/crypto-utils/model.d.ts +518 -10
  336. package/lib/packlets/crypto-utils/model.d.ts.map +1 -0
  337. package/lib/packlets/crypto-utils/model.js +33 -1
  338. package/lib/packlets/crypto-utils/model.js.map +1 -0
  339. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts +142 -2
  340. package/lib/packlets/crypto-utils/nodeCryptoProvider.d.ts.map +1 -0
  341. package/lib/packlets/crypto-utils/nodeCryptoProvider.js +346 -0
  342. package/lib/packlets/crypto-utils/nodeCryptoProvider.js.map +1 -0
  343. package/lib/packlets/crypto-utils/seedDerivedKeyPair.d.ts +42 -0
  344. package/lib/packlets/crypto-utils/seedDerivedKeyPair.d.ts.map +1 -0
  345. package/lib/packlets/crypto-utils/seedDerivedKeyPair.js +123 -0
  346. package/lib/packlets/crypto-utils/seedDerivedKeyPair.js.map +1 -0
  347. package/lib/packlets/crypto-utils/spkiHelpers.d.ts +151 -0
  348. package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -0
  349. package/lib/packlets/crypto-utils/spkiHelpers.js +297 -0
  350. package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -0
  351. package/lib/packlets/csv/csvFileHelpers.d.ts +0 -10
  352. package/lib/packlets/csv/csvFileHelpers.d.ts.map +1 -0
  353. package/lib/packlets/csv/csvFileHelpers.js +0 -15
  354. package/lib/packlets/csv/csvFileHelpers.js.map +1 -0
  355. package/lib/packlets/csv/csvHelpers.d.ts +10 -0
  356. package/lib/packlets/csv/csvHelpers.d.ts.map +1 -0
  357. package/lib/packlets/csv/csvHelpers.js +15 -0
  358. package/lib/packlets/csv/csvHelpers.js.map +1 -0
  359. package/lib/packlets/csv/index.browser.d.ts +0 -1
  360. package/lib/packlets/csv/index.browser.d.ts.map +1 -0
  361. package/lib/packlets/csv/index.browser.js +1 -5
  362. package/lib/packlets/csv/index.browser.js.map +1 -0
  363. package/lib/packlets/csv/index.d.ts.map +1 -0
  364. package/lib/packlets/csv/index.js.map +1 -0
  365. package/lib/packlets/experimental/extendedArray.d.ts.map +1 -0
  366. package/lib/packlets/experimental/extendedArray.js.map +1 -0
  367. package/lib/packlets/experimental/formatter.d.ts.map +1 -0
  368. package/lib/packlets/experimental/formatter.js.map +1 -0
  369. package/lib/packlets/experimental/index.d.ts.map +1 -0
  370. package/lib/packlets/experimental/index.js.map +1 -0
  371. package/lib/packlets/experimental/rangeOf.d.ts.map +1 -0
  372. package/lib/packlets/experimental/rangeOf.js.map +1 -0
  373. package/lib/packlets/hash/index.browser.d.ts.map +1 -0
  374. package/lib/packlets/hash/index.browser.js.map +1 -0
  375. package/lib/packlets/hash/index.d.ts.map +1 -0
  376. package/lib/packlets/hash/index.js.map +1 -0
  377. package/lib/packlets/hash/index.node.d.ts.map +1 -0
  378. package/lib/packlets/hash/index.node.js.map +1 -0
  379. package/lib/packlets/hash/md5Normalizer.browser.d.ts.map +1 -0
  380. package/lib/packlets/hash/md5Normalizer.browser.js.map +1 -0
  381. package/lib/packlets/hash/md5Normalizer.d.ts.map +1 -0
  382. package/lib/packlets/hash/md5Normalizer.js.map +1 -0
  383. package/lib/packlets/mustache/index.d.ts +1 -1
  384. package/lib/packlets/mustache/index.d.ts.map +1 -0
  385. package/lib/packlets/mustache/index.js.map +1 -0
  386. package/lib/packlets/mustache/interfaces.d.ts +34 -0
  387. package/lib/packlets/mustache/interfaces.d.ts.map +1 -0
  388. package/lib/packlets/mustache/interfaces.js.map +1 -0
  389. package/lib/packlets/mustache/mustacheTemplate.d.ts +2 -0
  390. package/lib/packlets/mustache/mustacheTemplate.d.ts.map +1 -0
  391. package/lib/packlets/mustache/mustacheTemplate.js +42 -4
  392. package/lib/packlets/mustache/mustacheTemplate.js.map +1 -0
  393. package/lib/packlets/record-jar/index.browser.d.ts +0 -1
  394. package/lib/packlets/record-jar/index.browser.d.ts.map +1 -0
  395. package/lib/packlets/record-jar/index.browser.js +1 -5
  396. package/lib/packlets/record-jar/index.browser.js.map +1 -0
  397. package/lib/packlets/record-jar/index.d.ts.map +1 -0
  398. package/lib/packlets/record-jar/index.js.map +1 -0
  399. package/lib/packlets/record-jar/recordJarFileHelpers.d.ts +0 -11
  400. package/lib/packlets/record-jar/recordJarFileHelpers.d.ts.map +1 -0
  401. package/lib/packlets/record-jar/recordJarFileHelpers.js +0 -19
  402. package/lib/packlets/record-jar/recordJarFileHelpers.js.map +1 -0
  403. package/lib/packlets/record-jar/recordJarHelpers.d.ts +11 -0
  404. package/lib/packlets/record-jar/recordJarHelpers.d.ts.map +1 -0
  405. package/lib/packlets/record-jar/recordJarHelpers.js +19 -0
  406. package/lib/packlets/record-jar/recordJarHelpers.js.map +1 -0
  407. package/lib/packlets/safer-fetch/addressClassification.d.ts +144 -0
  408. package/lib/packlets/safer-fetch/addressClassification.d.ts.map +1 -0
  409. package/lib/packlets/safer-fetch/addressClassification.js +441 -0
  410. package/lib/packlets/safer-fetch/addressClassification.js.map +1 -0
  411. package/lib/packlets/safer-fetch/addressPolicy.d.ts +113 -0
  412. package/lib/packlets/safer-fetch/addressPolicy.d.ts.map +1 -0
  413. package/lib/packlets/safer-fetch/addressPolicy.js +123 -0
  414. package/lib/packlets/safer-fetch/addressPolicy.js.map +1 -0
  415. package/lib/packlets/safer-fetch/contentType.d.ts +51 -0
  416. package/lib/packlets/safer-fetch/contentType.d.ts.map +1 -0
  417. package/lib/packlets/safer-fetch/contentType.js +127 -0
  418. package/lib/packlets/safer-fetch/contentType.js.map +1 -0
  419. package/lib/packlets/safer-fetch/deadline.d.ts +138 -0
  420. package/lib/packlets/safer-fetch/deadline.d.ts.map +1 -0
  421. package/lib/packlets/safer-fetch/deadline.js +258 -0
  422. package/lib/packlets/safer-fetch/deadline.js.map +1 -0
  423. package/lib/packlets/safer-fetch/defaults.d.ts +108 -0
  424. package/lib/packlets/safer-fetch/defaults.d.ts.map +1 -0
  425. package/lib/packlets/safer-fetch/defaults.js +134 -0
  426. package/lib/packlets/safer-fetch/defaults.js.map +1 -0
  427. package/lib/packlets/safer-fetch/failureReason.d.ts +148 -0
  428. package/lib/packlets/safer-fetch/failureReason.d.ts.map +1 -0
  429. package/lib/packlets/safer-fetch/failureReason.js +22 -0
  430. package/lib/packlets/safer-fetch/failureReason.js.map +1 -0
  431. package/lib/packlets/safer-fetch/guards.d.ts +62 -0
  432. package/lib/packlets/safer-fetch/guards.d.ts.map +1 -0
  433. package/lib/packlets/safer-fetch/guards.js +153 -0
  434. package/lib/packlets/safer-fetch/guards.js.map +1 -0
  435. package/lib/packlets/safer-fetch/index.browser.d.ts +33 -0
  436. package/lib/packlets/safer-fetch/index.browser.d.ts.map +1 -0
  437. package/lib/packlets/safer-fetch/index.browser.js +82 -0
  438. package/lib/packlets/safer-fetch/index.browser.js.map +1 -0
  439. package/lib/packlets/safer-fetch/index.d.ts +27 -0
  440. package/lib/packlets/safer-fetch/index.d.ts.map +1 -0
  441. package/lib/packlets/safer-fetch/index.js +78 -0
  442. package/lib/packlets/safer-fetch/index.js.map +1 -0
  443. package/lib/packlets/safer-fetch/model.d.ts +373 -0
  444. package/lib/packlets/safer-fetch/model.d.ts.map +1 -0
  445. package/lib/packlets/safer-fetch/model.js +22 -0
  446. package/lib/packlets/safer-fetch/model.js.map +1 -0
  447. package/lib/packlets/safer-fetch/nodeAddressGuard.d.ts +129 -0
  448. package/lib/packlets/safer-fetch/nodeAddressGuard.d.ts.map +1 -0
  449. package/lib/packlets/safer-fetch/nodeAddressGuard.js +196 -0
  450. package/lib/packlets/safer-fetch/nodeAddressGuard.js.map +1 -0
  451. package/lib/packlets/safer-fetch/redirect.d.ts +62 -0
  452. package/lib/packlets/safer-fetch/redirect.d.ts.map +1 -0
  453. package/lib/packlets/safer-fetch/redirect.js +98 -0
  454. package/lib/packlets/safer-fetch/redirect.js.map +1 -0
  455. package/lib/packlets/safer-fetch/retry.d.ts +162 -0
  456. package/lib/packlets/safer-fetch/retry.d.ts.map +1 -0
  457. package/lib/packlets/safer-fetch/retry.js +197 -0
  458. package/lib/packlets/safer-fetch/retry.js.map +1 -0
  459. package/lib/packlets/safer-fetch/saferFetch.d.ts +108 -0
  460. package/lib/packlets/safer-fetch/saferFetch.d.ts.map +1 -0
  461. package/lib/packlets/safer-fetch/saferFetch.js +760 -0
  462. package/lib/packlets/safer-fetch/saferFetch.js.map +1 -0
  463. package/lib/packlets/safer-fetch/transport.d.ts +18 -0
  464. package/lib/packlets/safer-fetch/transport.d.ts.map +1 -0
  465. package/lib/packlets/safer-fetch/transport.js +53 -0
  466. package/lib/packlets/safer-fetch/transport.js.map +1 -0
  467. package/lib/packlets/yaml/converters.d.ts.map +1 -0
  468. package/lib/packlets/yaml/converters.js.map +1 -0
  469. package/lib/packlets/yaml/index.d.ts +1 -0
  470. package/lib/packlets/yaml/index.d.ts.map +1 -0
  471. package/lib/packlets/yaml/index.js +1 -0
  472. package/lib/packlets/yaml/index.js.map +1 -0
  473. package/lib/packlets/yaml/serializers.d.ts +45 -0
  474. package/lib/packlets/yaml/serializers.d.ts.map +1 -0
  475. package/lib/packlets/yaml/serializers.js +84 -0
  476. package/lib/packlets/yaml/serializers.js.map +1 -0
  477. package/lib/packlets/zip-file-tree/index.d.ts.map +1 -0
  478. package/lib/packlets/zip-file-tree/index.js.map +1 -0
  479. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts +84 -8
  480. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.d.ts.map +1 -0
  481. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js +99 -9
  482. package/lib/packlets/zip-file-tree/zipFileTreeAccessors.js.map +1 -0
  483. package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts +26 -1
  484. package/lib/packlets/zip-file-tree/zipFileTreeWriter.d.ts.map +1 -0
  485. package/lib/packlets/zip-file-tree/zipFileTreeWriter.js +17 -1
  486. package/lib/packlets/zip-file-tree/zipFileTreeWriter.js.map +1 -0
  487. package/package.json +30 -15
  488. package/lib/packlets/ai-assist/apiClient.d.ts +0 -60
@@ -0,0 +1,162 @@
1
+ import { type Result } from '@fgv/ts-utils';
2
+ import type { FetchFailureReason } from './failureReason';
3
+ import type { SaferFetchMethod } from './model';
4
+ /**
5
+ * Opt-in retry policy.
6
+ *
7
+ * @remarks
8
+ * **Retry is off by default and has to be asked for.** A primitive that silently retries
9
+ * changes the semantics of every call site and amplifies load against a service that is already
10
+ * struggling.
11
+ *
12
+ * Two rules are load-bearing rather than incidental, and are worth reading before enabling this:
13
+ *
14
+ * **Every attempt re-runs the address guard from hop 0 — a full re-walk, never a resume and
15
+ * never a cached verdict.** Reusing an earlier attempt's verdict would make retry its own
16
+ * DNS-rebinding vector: an attacker needs the guard to pass once and a connect to land on a
17
+ * private address, so `N` connects against **one** check multiplies their odds by the retry
18
+ * count — and a retry delay is precisely when a short-TTL rebind lands. A resume is
19
+ * independently wrong because the redirect chain is not stable across attempts: attempt 2's
20
+ * server may answer with a different `Location`, so revalidating only the current hop would
21
+ * validate a chain the request no longer follows. The consequence is that retries cost more
22
+ * than a naive implementation would suggest — each carries full re-resolution — and that cost
23
+ * is deliberate.
24
+ *
25
+ * **The overall deadline is the ceiling.** Retries consume `timeoutMs` and never reset it, so
26
+ * `{ timeoutMs: 30_000, retry: { attempts: 3 } }` is a thirty-second call, not a two-minute
27
+ * one. When the remaining budget is shorter than the computed delay, the call fails with the
28
+ * attempt's own failure rather than sleeping past its deadline.
29
+ * @public
30
+ */
31
+ export interface IRetryPolicy {
32
+ /**
33
+ * Additional attempts after the first, **not** a total. `attempts: 2` means up to three
34
+ * requests. Must be a non-negative integer; `0` disables retry.
35
+ */
36
+ readonly attempts: number;
37
+ /**
38
+ * First backoff delay, in milliseconds, doubled per attempt before jitter. Default
39
+ * {@link SaferFetch.DEFAULT_RETRY_BASE_DELAY_MS}.
40
+ */
41
+ readonly baseDelayMs?: number;
42
+ /**
43
+ * Ceiling on any single delay, in milliseconds. Default
44
+ * {@link SaferFetch.DEFAULT_RETRY_MAX_DELAY_MS}. Also clamps a server-supplied
45
+ * `Retry-After`, which is the security-relevant half of its job.
46
+ */
47
+ readonly maxDelayMs?: number;
48
+ /**
49
+ * Retries `POST` / `PUT` / `PATCH` / `DELETE` as well as `GET` / `HEAD`. Default `false`.
50
+ *
51
+ * @remarks
52
+ * Off by default because a timeout does not tell you whether the server processed the
53
+ * request: a retried `POST` after a timeout can double-charge. Turn it on for endpoints you
54
+ * know are idempotent — an idempotency key, a `PUT` of a whole resource — and not otherwise.
55
+ */
56
+ readonly retryNonIdempotent?: boolean;
57
+ /**
58
+ * Honors a `Retry-After` header on `429` and `503`. Default `true`.
59
+ *
60
+ * @remarks
61
+ * Always clamped to `maxDelayMs`, whatever this is set to. The header is chosen by the
62
+ * server, which in this threat model may be the adversary.
63
+ */
64
+ readonly respectRetryAfter?: boolean;
65
+ }
66
+ /**
67
+ * A {@link SaferFetch.IRetryPolicy} with every default applied, so no downstream code path
68
+ * branches on a field's absence.
69
+ * @internal
70
+ */
71
+ export interface IResolvedRetryPolicy {
72
+ readonly attempts: number;
73
+ readonly baseDelayMs: number;
74
+ readonly maxDelayMs: number;
75
+ readonly retryNonIdempotent: boolean;
76
+ readonly respectRetryAfter: boolean;
77
+ }
78
+ /** No retry. The resolved shape of an absent `retry` option. @internal */
79
+ export declare const NO_RETRY: IResolvedRetryPolicy;
80
+ /**
81
+ * Applies the retry defaults, rejecting a policy that cannot mean anything.
82
+ *
83
+ * @remarks
84
+ * Validated for the same reason `redirectPolicy` and `sensitiveHeaders` are: a JavaScript
85
+ * caller can pass anything, and a `NaN` delay silently becomes a `setTimeout(NaN)` that fires
86
+ * immediately — turning a backoff into a tight retry loop against a struggling service.
87
+ * @internal
88
+ */
89
+ export declare function resolveRetryPolicy(retry?: IRetryPolicy): Result<IResolvedRetryPolicy>;
90
+ /**
91
+ * Decides whether a failure is one a later attempt could plausibly answer differently.
92
+ *
93
+ * @remarks
94
+ * The `switch` is exhaustive over the taxonomy on purpose: a new failure kind should have to be
95
+ * classified deliberately rather than inherit "retryable" or "not" from a default branch.
96
+ *
97
+ * `'blocked-by-guard'` can never succeed on a retry — the guard's answer is about the
98
+ * destination, not about the moment. `'too-large'` is the sharper case: retrying it
99
+ * re-downloads up to the cap every time, which turns a defense into an amplifier aimed at the
100
+ * caller's own bandwidth.
101
+ * @internal
102
+ */
103
+ export declare function isRetryableFailure(reason: FetchFailureReason | undefined): boolean;
104
+ /**
105
+ * Whether this method may be retried under the supplied policy.
106
+ * @internal
107
+ */
108
+ export declare function isRetryableMethod(method: SaferFetchMethod, policy: IResolvedRetryPolicy): boolean;
109
+ /**
110
+ * Whether a `Retry-After` header on this failure is one to honor.
111
+ *
112
+ * @remarks
113
+ * Only on `429` and `503`, and only when the policy asks for it. A `500` carrying a
114
+ * `Retry-After` does not get to dictate the caller's schedule.
115
+ * @internal
116
+ */
117
+ export declare function honorsRetryAfter(reason: FetchFailureReason | undefined, policy: IResolvedRetryPolicy): boolean;
118
+ /**
119
+ * Parses a `Retry-After` header value into milliseconds.
120
+ *
121
+ * @remarks
122
+ * Both RFC 9110 forms: delta-seconds, and an HTTP-date which is converted to a delay relative
123
+ * to `nowMs`. An unparseable value, a negative delta, or a date already in the past yields
124
+ * `undefined` — "the server said nothing usable", which falls back to ordinary backoff rather
125
+ * than to zero delay. Returning `0` for a past date would let a stale clock produce a hot loop.
126
+ *
127
+ * The value is **not** clamped here. Clamping belongs with the rest of the delay arithmetic in
128
+ * {@link computeRetryDelayMs}, so there is exactly one place the ceiling is applied.
129
+ * @internal
130
+ */
131
+ export declare function parseRetryAfterMs(value: string | undefined, nowMs: number): number | undefined;
132
+ /** Inputs to {@link computeRetryDelayMs}. @internal */
133
+ export interface IRetryDelayParams {
134
+ /** 0-based index of the retry about to be scheduled: `0` is the first retry. */
135
+ readonly attempt: number;
136
+ readonly policy: IResolvedRetryPolicy;
137
+ /** A honored, parsed `Retry-After`, in milliseconds, when the server sent a usable one. */
138
+ readonly retryAfterMs?: number;
139
+ /** Injected for determinism in tests; the call path passes `Math.random`. */
140
+ readonly random: () => number;
141
+ }
142
+ /**
143
+ * Computes how long to wait before the next attempt.
144
+ *
145
+ * @remarks
146
+ * **Exponential backoff with full jitter**: the delay is uniform on `[0, cap)` where `cap` is
147
+ * `baseDelayMs * 2^attempt` bounded by `maxDelayMs`. Full jitter rather than a fixed backoff
148
+ * because every caller that failed against the same struggling host would otherwise retry in
149
+ * the same instant, which is the thundering herd the backoff was supposed to prevent.
150
+ *
151
+ * **A honored `Retry-After` replaces the computed backoff and is clamped to `maxDelayMs`.** The
152
+ * clamp is not a rounding detail: `Retry-After` is chosen by the server, which in this threat
153
+ * model may be the adversary, so an unclamped `Retry-After: 86400` is a day-long sleep the
154
+ * caller never agreed to. Jitter is not applied to it — the server named an instant, and
155
+ * scattering around it is the caller's herd problem, not the server's instruction.
156
+ *
157
+ * `2 ** attempt` overflows to `Infinity` for a large enough `attempt`; `Math.min` with a finite
158
+ * ceiling absorbs that, which is why the cap is computed in this order.
159
+ * @internal
160
+ */
161
+ export declare function computeRetryDelayMs(params: IRetryDelayParams): number;
162
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../../src/packlets/safer-fetch/retry.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAS3D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B;;;;;;;OAOG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAEtC;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;CACrC;AAED,0EAA0E;AAC1E,eAAO,MAAM,QAAQ,EAAE,oBAMtB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAkCrF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAyBlF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAEjG;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,kBAAkB,GAAG,SAAS,EACtC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAIT;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB9F;AAED,uDAAuD;AACvD,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,2FAA2F;IAC3F,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,MAAM,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAQrE"}
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+ // Copyright (c) 2026 Erik Fortune
3
+ //
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ // of this software and associated documentation files (the "Software"), to deal
6
+ // in the Software without restriction, including without limitation the rights
7
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ // copies of the Software, and to permit persons to whom the Software is
9
+ // furnished to do so, subject to the following conditions:
10
+ //
11
+ // The above copyright notice and this permission notice shall be included in all
12
+ // copies or substantial portions of the Software.
13
+ //
14
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ // SOFTWARE.
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.NO_RETRY = void 0;
23
+ exports.resolveRetryPolicy = resolveRetryPolicy;
24
+ exports.isRetryableFailure = isRetryableFailure;
25
+ exports.isRetryableMethod = isRetryableMethod;
26
+ exports.honorsRetryAfter = honorsRetryAfter;
27
+ exports.parseRetryAfterMs = parseRetryAfterMs;
28
+ exports.computeRetryDelayMs = computeRetryDelayMs;
29
+ const ts_utils_1 = require("@fgv/ts-utils");
30
+ const defaults_1 = require("./defaults");
31
+ /** No retry. The resolved shape of an absent `retry` option. @internal */
32
+ exports.NO_RETRY = {
33
+ attempts: 0,
34
+ baseDelayMs: defaults_1.DEFAULT_RETRY_BASE_DELAY_MS,
35
+ maxDelayMs: defaults_1.DEFAULT_RETRY_MAX_DELAY_MS,
36
+ retryNonIdempotent: false,
37
+ respectRetryAfter: true
38
+ };
39
+ /**
40
+ * Applies the retry defaults, rejecting a policy that cannot mean anything.
41
+ *
42
+ * @remarks
43
+ * Validated for the same reason `redirectPolicy` and `sensitiveHeaders` are: a JavaScript
44
+ * caller can pass anything, and a `NaN` delay silently becomes a `setTimeout(NaN)` that fires
45
+ * immediately — turning a backoff into a tight retry loop against a struggling service.
46
+ * @internal
47
+ */
48
+ function resolveRetryPolicy(retry) {
49
+ var _a, _b, _c;
50
+ // `?? undefined` rather than `!== undefined`, matching every other option: a `null` from a
51
+ // JavaScript caller means "absent", never "installed".
52
+ const policy = retry !== null && retry !== void 0 ? retry : undefined;
53
+ if (policy === undefined) {
54
+ return (0, ts_utils_1.succeed)(exports.NO_RETRY);
55
+ }
56
+ const attempts = policy.attempts;
57
+ if (!Number.isInteger(attempts) || attempts < 0) {
58
+ return (0, ts_utils_1.fail)(`retry.attempts must be a non-negative integer; got ${attempts}.`);
59
+ }
60
+ const baseDelayMs = (_a = policy.baseDelayMs) !== null && _a !== void 0 ? _a : defaults_1.DEFAULT_RETRY_BASE_DELAY_MS;
61
+ const maxDelayMs = (_b = policy.maxDelayMs) !== null && _b !== void 0 ? _b : defaults_1.DEFAULT_RETRY_MAX_DELAY_MS;
62
+ const delays = [
63
+ ['retry.baseDelayMs', baseDelayMs],
64
+ ['retry.maxDelayMs', maxDelayMs]
65
+ ];
66
+ for (const [name, value] of delays) {
67
+ if (!Number.isFinite(value) || value < 0) {
68
+ return (0, ts_utils_1.fail)(`${name} must be a non-negative finite number; got ${value}.`);
69
+ }
70
+ }
71
+ return (0, ts_utils_1.succeed)({
72
+ attempts,
73
+ baseDelayMs,
74
+ maxDelayMs,
75
+ retryNonIdempotent: policy.retryNonIdempotent === true,
76
+ // Defaults to honoring the header, so `?? true` rather than the `=== true` the other two
77
+ // flags use.
78
+ respectRetryAfter: (_c = policy.respectRetryAfter) !== null && _c !== void 0 ? _c : true
79
+ });
80
+ }
81
+ /**
82
+ * Decides whether a failure is one a later attempt could plausibly answer differently.
83
+ *
84
+ * @remarks
85
+ * The `switch` is exhaustive over the taxonomy on purpose: a new failure kind should have to be
86
+ * classified deliberately rather than inherit "retryable" or "not" from a default branch.
87
+ *
88
+ * `'blocked-by-guard'` can never succeed on a retry — the guard's answer is about the
89
+ * destination, not about the moment. `'too-large'` is the sharper case: retrying it
90
+ * re-downloads up to the cap every time, which turns a defense into an amplifier aimed at the
91
+ * caller's own bandwidth.
92
+ * @internal
93
+ */
94
+ function isRetryableFailure(reason) {
95
+ // A failure with no detail is one this primitive could not classify, and an unclassifiable
96
+ // failure is not one to retry — the whole basis for retrying is knowing what went wrong.
97
+ if (reason === undefined) {
98
+ return false;
99
+ }
100
+ switch (reason.kind) {
101
+ case 'network':
102
+ case 'timeout':
103
+ return true;
104
+ case 'http-status':
105
+ return defaults_1.RETRYABLE_HTTP_STATUSES.includes(reason.status);
106
+ case 'invalid-url':
107
+ case 'blocked-by-guard':
108
+ case 'redirect-rejected':
109
+ case 'redirect-opaque':
110
+ case 'too-many-redirects':
111
+ case 'aborted':
112
+ case 'too-large':
113
+ case 'unsupported-content-type':
114
+ case 'decode':
115
+ case 'parse':
116
+ case 'unknown':
117
+ return false;
118
+ }
119
+ }
120
+ /**
121
+ * Whether this method may be retried under the supplied policy.
122
+ * @internal
123
+ */
124
+ function isRetryableMethod(method, policy) {
125
+ return policy.retryNonIdempotent || defaults_1.IDEMPOTENT_METHODS.includes(method);
126
+ }
127
+ /**
128
+ * Whether a `Retry-After` header on this failure is one to honor.
129
+ *
130
+ * @remarks
131
+ * Only on `429` and `503`, and only when the policy asks for it. A `500` carrying a
132
+ * `Retry-After` does not get to dictate the caller's schedule.
133
+ * @internal
134
+ */
135
+ function honorsRetryAfter(reason, policy) {
136
+ return (policy.respectRetryAfter && (reason === null || reason === void 0 ? void 0 : reason.kind) === 'http-status' && defaults_1.RETRY_AFTER_STATUSES.includes(reason.status));
137
+ }
138
+ /**
139
+ * Parses a `Retry-After` header value into milliseconds.
140
+ *
141
+ * @remarks
142
+ * Both RFC 9110 forms: delta-seconds, and an HTTP-date which is converted to a delay relative
143
+ * to `nowMs`. An unparseable value, a negative delta, or a date already in the past yields
144
+ * `undefined` — "the server said nothing usable", which falls back to ordinary backoff rather
145
+ * than to zero delay. Returning `0` for a past date would let a stale clock produce a hot loop.
146
+ *
147
+ * The value is **not** clamped here. Clamping belongs with the rest of the delay arithmetic in
148
+ * {@link computeRetryDelayMs}, so there is exactly one place the ceiling is applied.
149
+ * @internal
150
+ */
151
+ function parseRetryAfterMs(value, nowMs) {
152
+ const text = (value !== null && value !== void 0 ? value : '').trim();
153
+ if (text.length === 0) {
154
+ return undefined;
155
+ }
156
+ // Digits only: a delta-seconds value. Tested before `Date.parse`, which accepts bare numbers
157
+ // as years in some engines and would read `Retry-After: 120` as the year 120.
158
+ if (/^\d+$/.test(text)) {
159
+ return Number(text) * 1000;
160
+ }
161
+ const at = Date.parse(text);
162
+ if (Number.isNaN(at)) {
163
+ return undefined;
164
+ }
165
+ const delay = at - nowMs;
166
+ return delay > 0 ? delay : undefined;
167
+ }
168
+ /**
169
+ * Computes how long to wait before the next attempt.
170
+ *
171
+ * @remarks
172
+ * **Exponential backoff with full jitter**: the delay is uniform on `[0, cap)` where `cap` is
173
+ * `baseDelayMs * 2^attempt` bounded by `maxDelayMs`. Full jitter rather than a fixed backoff
174
+ * because every caller that failed against the same struggling host would otherwise retry in
175
+ * the same instant, which is the thundering herd the backoff was supposed to prevent.
176
+ *
177
+ * **A honored `Retry-After` replaces the computed backoff and is clamped to `maxDelayMs`.** The
178
+ * clamp is not a rounding detail: `Retry-After` is chosen by the server, which in this threat
179
+ * model may be the adversary, so an unclamped `Retry-After: 86400` is a day-long sleep the
180
+ * caller never agreed to. Jitter is not applied to it — the server named an instant, and
181
+ * scattering around it is the caller's herd problem, not the server's instruction.
182
+ *
183
+ * `2 ** attempt` overflows to `Infinity` for a large enough `attempt`; `Math.min` with a finite
184
+ * ceiling absorbs that, which is why the cap is computed in this order.
185
+ * @internal
186
+ */
187
+ function computeRetryDelayMs(params) {
188
+ var _a;
189
+ const { attempt, policy, random } = params;
190
+ const retryAfterMs = (_a = params.retryAfterMs) !== null && _a !== void 0 ? _a : undefined;
191
+ if (retryAfterMs !== undefined) {
192
+ return Math.min(retryAfterMs, policy.maxDelayMs);
193
+ }
194
+ const cap = Math.min(policy.baseDelayMs * 2 ** attempt, policy.maxDelayMs);
195
+ return random() * cap;
196
+ }
197
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/packlets/safer-fetch/retry.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) 2026 Erik Fortune\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n\nimport { fail, succeed, type Result } from '@fgv/ts-utils';\n\nimport {\n DEFAULT_RETRY_BASE_DELAY_MS,\n DEFAULT_RETRY_MAX_DELAY_MS,\n IDEMPOTENT_METHODS,\n RETRY_AFTER_STATUSES,\n RETRYABLE_HTTP_STATUSES\n} from './defaults';\nimport type { FetchFailureReason } from './failureReason';\nimport type { SaferFetchMethod } from './model';\n\n/**\n * Opt-in retry policy.\n *\n * @remarks\n * **Retry is off by default and has to be asked for.** A primitive that silently retries\n * changes the semantics of every call site and amplifies load against a service that is already\n * struggling.\n *\n * Two rules are load-bearing rather than incidental, and are worth reading before enabling this:\n *\n * **Every attempt re-runs the address guard from hop 0 — a full re-walk, never a resume and\n * never a cached verdict.** Reusing an earlier attempt's verdict would make retry its own\n * DNS-rebinding vector: an attacker needs the guard to pass once and a connect to land on a\n * private address, so `N` connects against **one** check multiplies their odds by the retry\n * count — and a retry delay is precisely when a short-TTL rebind lands. A resume is\n * independently wrong because the redirect chain is not stable across attempts: attempt 2's\n * server may answer with a different `Location`, so revalidating only the current hop would\n * validate a chain the request no longer follows. The consequence is that retries cost more\n * than a naive implementation would suggest — each carries full re-resolution — and that cost\n * is deliberate.\n *\n * **The overall deadline is the ceiling.** Retries consume `timeoutMs` and never reset it, so\n * `{ timeoutMs: 30_000, retry: { attempts: 3 } }` is a thirty-second call, not a two-minute\n * one. When the remaining budget is shorter than the computed delay, the call fails with the\n * attempt's own failure rather than sleeping past its deadline.\n * @public\n */\nexport interface IRetryPolicy {\n /**\n * Additional attempts after the first, **not** a total. `attempts: 2` means up to three\n * requests. Must be a non-negative integer; `0` disables retry.\n */\n readonly attempts: number;\n\n /**\n * First backoff delay, in milliseconds, doubled per attempt before jitter. Default\n * {@link SaferFetch.DEFAULT_RETRY_BASE_DELAY_MS}.\n */\n readonly baseDelayMs?: number;\n\n /**\n * Ceiling on any single delay, in milliseconds. Default\n * {@link SaferFetch.DEFAULT_RETRY_MAX_DELAY_MS}. Also clamps a server-supplied\n * `Retry-After`, which is the security-relevant half of its job.\n */\n readonly maxDelayMs?: number;\n\n /**\n * Retries `POST` / `PUT` / `PATCH` / `DELETE` as well as `GET` / `HEAD`. Default `false`.\n *\n * @remarks\n * Off by default because a timeout does not tell you whether the server processed the\n * request: a retried `POST` after a timeout can double-charge. Turn it on for endpoints you\n * know are idempotent — an idempotency key, a `PUT` of a whole resource — and not otherwise.\n */\n readonly retryNonIdempotent?: boolean;\n\n /**\n * Honors a `Retry-After` header on `429` and `503`. Default `true`.\n *\n * @remarks\n * Always clamped to `maxDelayMs`, whatever this is set to. The header is chosen by the\n * server, which in this threat model may be the adversary.\n */\n readonly respectRetryAfter?: boolean;\n}\n\n/**\n * A {@link SaferFetch.IRetryPolicy} with every default applied, so no downstream code path\n * branches on a field's absence.\n * @internal\n */\nexport interface IResolvedRetryPolicy {\n readonly attempts: number;\n readonly baseDelayMs: number;\n readonly maxDelayMs: number;\n readonly retryNonIdempotent: boolean;\n readonly respectRetryAfter: boolean;\n}\n\n/** No retry. The resolved shape of an absent `retry` option. @internal */\nexport const NO_RETRY: IResolvedRetryPolicy = {\n attempts: 0,\n baseDelayMs: DEFAULT_RETRY_BASE_DELAY_MS,\n maxDelayMs: DEFAULT_RETRY_MAX_DELAY_MS,\n retryNonIdempotent: false,\n respectRetryAfter: true\n};\n\n/**\n * Applies the retry defaults, rejecting a policy that cannot mean anything.\n *\n * @remarks\n * Validated for the same reason `redirectPolicy` and `sensitiveHeaders` are: a JavaScript\n * caller can pass anything, and a `NaN` delay silently becomes a `setTimeout(NaN)` that fires\n * immediately — turning a backoff into a tight retry loop against a struggling service.\n * @internal\n */\nexport function resolveRetryPolicy(retry?: IRetryPolicy): Result<IResolvedRetryPolicy> {\n // `?? undefined` rather than `!== undefined`, matching every other option: a `null` from a\n // JavaScript caller means \"absent\", never \"installed\".\n const policy = retry ?? undefined;\n if (policy === undefined) {\n return succeed(NO_RETRY);\n }\n\n const attempts = policy.attempts;\n if (!Number.isInteger(attempts) || attempts < 0) {\n return fail(`retry.attempts must be a non-negative integer; got ${attempts}.`);\n }\n\n const baseDelayMs = policy.baseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;\n const maxDelayMs = policy.maxDelayMs ?? DEFAULT_RETRY_MAX_DELAY_MS;\n const delays: ReadonlyArray<readonly [string, number]> = [\n ['retry.baseDelayMs', baseDelayMs],\n ['retry.maxDelayMs', maxDelayMs]\n ];\n for (const [name, value] of delays) {\n if (!Number.isFinite(value) || value < 0) {\n return fail(`${name} must be a non-negative finite number; got ${value}.`);\n }\n }\n\n return succeed({\n attempts,\n baseDelayMs,\n maxDelayMs,\n retryNonIdempotent: policy.retryNonIdempotent === true,\n // Defaults to honoring the header, so `?? true` rather than the `=== true` the other two\n // flags use.\n respectRetryAfter: policy.respectRetryAfter ?? true\n });\n}\n\n/**\n * Decides whether a failure is one a later attempt could plausibly answer differently.\n *\n * @remarks\n * The `switch` is exhaustive over the taxonomy on purpose: a new failure kind should have to be\n * classified deliberately rather than inherit \"retryable\" or \"not\" from a default branch.\n *\n * `'blocked-by-guard'` can never succeed on a retry — the guard's answer is about the\n * destination, not about the moment. `'too-large'` is the sharper case: retrying it\n * re-downloads up to the cap every time, which turns a defense into an amplifier aimed at the\n * caller's own bandwidth.\n * @internal\n */\nexport function isRetryableFailure(reason: FetchFailureReason | undefined): boolean {\n // A failure with no detail is one this primitive could not classify, and an unclassifiable\n // failure is not one to retry — the whole basis for retrying is knowing what went wrong.\n if (reason === undefined) {\n return false;\n }\n switch (reason.kind) {\n case 'network':\n case 'timeout':\n return true;\n case 'http-status':\n return RETRYABLE_HTTP_STATUSES.includes(reason.status);\n case 'invalid-url':\n case 'blocked-by-guard':\n case 'redirect-rejected':\n case 'redirect-opaque':\n case 'too-many-redirects':\n case 'aborted':\n case 'too-large':\n case 'unsupported-content-type':\n case 'decode':\n case 'parse':\n case 'unknown':\n return false;\n }\n}\n\n/**\n * Whether this method may be retried under the supplied policy.\n * @internal\n */\nexport function isRetryableMethod(method: SaferFetchMethod, policy: IResolvedRetryPolicy): boolean {\n return policy.retryNonIdempotent || IDEMPOTENT_METHODS.includes(method);\n}\n\n/**\n * Whether a `Retry-After` header on this failure is one to honor.\n *\n * @remarks\n * Only on `429` and `503`, and only when the policy asks for it. A `500` carrying a\n * `Retry-After` does not get to dictate the caller's schedule.\n * @internal\n */\nexport function honorsRetryAfter(\n reason: FetchFailureReason | undefined,\n policy: IResolvedRetryPolicy\n): boolean {\n return (\n policy.respectRetryAfter && reason?.kind === 'http-status' && RETRY_AFTER_STATUSES.includes(reason.status)\n );\n}\n\n/**\n * Parses a `Retry-After` header value into milliseconds.\n *\n * @remarks\n * Both RFC 9110 forms: delta-seconds, and an HTTP-date which is converted to a delay relative\n * to `nowMs`. An unparseable value, a negative delta, or a date already in the past yields\n * `undefined` — \"the server said nothing usable\", which falls back to ordinary backoff rather\n * than to zero delay. Returning `0` for a past date would let a stale clock produce a hot loop.\n *\n * The value is **not** clamped here. Clamping belongs with the rest of the delay arithmetic in\n * {@link computeRetryDelayMs}, so there is exactly one place the ceiling is applied.\n * @internal\n */\nexport function parseRetryAfterMs(value: string | undefined, nowMs: number): number | undefined {\n const text = (value ?? '').trim();\n if (text.length === 0) {\n return undefined;\n }\n // Digits only: a delta-seconds value. Tested before `Date.parse`, which accepts bare numbers\n // as years in some engines and would read `Retry-After: 120` as the year 120.\n if (/^\\d+$/.test(text)) {\n return Number(text) * 1000;\n }\n const at = Date.parse(text);\n if (Number.isNaN(at)) {\n return undefined;\n }\n const delay = at - nowMs;\n return delay > 0 ? delay : undefined;\n}\n\n/** Inputs to {@link computeRetryDelayMs}. @internal */\nexport interface IRetryDelayParams {\n /** 0-based index of the retry about to be scheduled: `0` is the first retry. */\n readonly attempt: number;\n readonly policy: IResolvedRetryPolicy;\n /** A honored, parsed `Retry-After`, in milliseconds, when the server sent a usable one. */\n readonly retryAfterMs?: number;\n /** Injected for determinism in tests; the call path passes `Math.random`. */\n readonly random: () => number;\n}\n\n/**\n * Computes how long to wait before the next attempt.\n *\n * @remarks\n * **Exponential backoff with full jitter**: the delay is uniform on `[0, cap)` where `cap` is\n * `baseDelayMs * 2^attempt` bounded by `maxDelayMs`. Full jitter rather than a fixed backoff\n * because every caller that failed against the same struggling host would otherwise retry in\n * the same instant, which is the thundering herd the backoff was supposed to prevent.\n *\n * **A honored `Retry-After` replaces the computed backoff and is clamped to `maxDelayMs`.** The\n * clamp is not a rounding detail: `Retry-After` is chosen by the server, which in this threat\n * model may be the adversary, so an unclamped `Retry-After: 86400` is a day-long sleep the\n * caller never agreed to. Jitter is not applied to it — the server named an instant, and\n * scattering around it is the caller's herd problem, not the server's instruction.\n *\n * `2 ** attempt` overflows to `Infinity` for a large enough `attempt`; `Math.min` with a finite\n * ceiling absorbs that, which is why the cap is computed in this order.\n * @internal\n */\nexport function computeRetryDelayMs(params: IRetryDelayParams): number {\n const { attempt, policy, random } = params;\n const retryAfterMs = params.retryAfterMs ?? undefined;\n if (retryAfterMs !== undefined) {\n return Math.min(retryAfterMs, policy.maxDelayMs);\n }\n const cap = Math.min(policy.baseDelayMs * 2 ** attempt, policy.maxDelayMs);\n return random() * cap;\n}\n"]}
@@ -0,0 +1,108 @@
1
+ import { type Converter, type DetailedResult, type Validator } from '@fgv/ts-utils';
2
+ import { type JsonValue } from '@fgv/ts-json-base';
3
+ import type { FetchFailureReason } from './failureReason';
4
+ import type { ISaferFetchOptions, ISaferFetchResponse } from './model';
5
+ /**
6
+ * Fetches a URL and returns the raw response bytes, subject to the scheme check, the address
7
+ * guard, the request and response guards, the deadlines, and the response size cap.
8
+ *
9
+ * @remarks
10
+ * **What this does not protect against.** It is not a sandbox: a caller who wants to reach
11
+ * `127.0.0.1` can call `globalThis.fetch` directly, so this guards untrusted *data*, never
12
+ * untrusted *code in the same process*. It does no certificate pinning and adds nothing
13
+ * against a network-position attacker — TLS is that control. It does not resist DNS rebinding:
14
+ * an address guard validates a resolved address and the transport then re-resolves, so a
15
+ * hostile resolver can answer the two lookups differently. And it inspects nothing in the
16
+ * response body. If your deployment has an egress proxy or firewall, that control is strictly
17
+ * stronger than this one; this is defense in depth for deployments that do not.
18
+ *
19
+ * **The failure detail is an internal-network scanning oracle.** Log it; do not echo it, or
20
+ * any string derived from it, to an untrusted caller. The detail is structured precisely so
21
+ * that mapping it to a coarse public code is trivial.
22
+ *
23
+ * **Redirects are rejected unless you ask for them.** `redirectPolicy: 'validate-each-hop'`
24
+ * follows them, runs the address guard on every hop before any connection, and drops credential
25
+ * headers the first time the chain leaves an origin — and never restores them, so an
26
+ * `A` → `B` → `A` chain does not hand the token back to `A`.
27
+ *
28
+ * @param url - The URL to fetch. Only `http:` and `https:` are ever requested; every other
29
+ * scheme fails as `'invalid-url'`.
30
+ * @param options - Call options. `addressGuard` is required and has no default — use
31
+ * `blockPrivateNetworks()` on Node, or `allowAnyAddress()` to name the absence of the
32
+ * guarantee at the call site. `maxResponseBytes` defaults to 5 MiB and is meant to be tuned
33
+ * per call: real documents clear that, and raising it for the calls that need it is the
34
+ * intended use, not a workaround.
35
+ * @public
36
+ */
37
+ export declare function saferFetchBytes(url: string | URL, options: ISaferFetchOptions): Promise<DetailedResult<ISaferFetchResponse<Uint8Array>, FetchFailureReason>>;
38
+ /**
39
+ * Fetches a URL and decodes the response body as text.
40
+ *
41
+ * @remarks
42
+ * The charset comes from the `Content-Type` parameter and defaults to UTF-8. Decoding is
43
+ * strict: an unknown charset, or a byte sequence that is not valid in the declared one, fails
44
+ * as `'decode'` rather than silently producing mojibake that some caller downstream treats as
45
+ * data.
46
+ *
47
+ * See {@link SaferFetch.saferFetchBytes} for what this primitive does **not** protect against,
48
+ * and for the warning about echoing failure detail to untrusted callers.
49
+ *
50
+ * @param url - The URL to fetch.
51
+ * @param options - Call options. `addressGuard` is required. `maxResponseBytes` defaults to
52
+ * 5 MiB and is meant to be tuned per call.
53
+ * @public
54
+ */
55
+ export declare function saferFetchText(url: string | URL, options: ISaferFetchOptions): Promise<DetailedResult<ISaferFetchResponse<string>, FetchFailureReason>>;
56
+ /**
57
+ * Options for the validating form of {@link SaferFetch.saferFetchJson}.
58
+ *
59
+ * @remarks
60
+ * `converter` is **required** here, and `T` is inferred from it. There is deliberately no way
61
+ * to assert a `T` without supplying the converter that evidences it at runtime: a caller-named
62
+ * type with nothing checking it is a claim the primitive cannot keep. Omit the whole options
63
+ * type and the value comes back as `JsonValue`, which is what the wire actually guarantees.
64
+ * @public
65
+ */
66
+ export interface ISaferFetchJsonOptions<T> extends ISaferFetchOptions {
67
+ /** Applied to the parsed JSON, taking the caller from wire to validated `T` in one step. */
68
+ readonly converter: Converter<T> | Validator<T>;
69
+ }
70
+ /**
71
+ * Fetches a URL and parses the response body as JSON, yielding the raw `JsonValue` for the
72
+ * caller to validate.
73
+ *
74
+ * @remarks
75
+ * **This does not gate on `Content-Type` by itself.** A server returning an HTML error page
76
+ * with a `200` will be decoded and parsed, and fail as `'parse'` — twenty frames from where it
77
+ * actually went wrong. Pass `responseHeadersGuard: allowContentTypes(['application/json'])`
78
+ * to reject on the header instead: it costs a header comparison rather than a body transfer,
79
+ * and the failure names the type. Content-type gating is a guard rather than an option so that
80
+ * there is exactly one mechanism for it.
81
+ *
82
+ * See {@link SaferFetch.saferFetchBytes} for what this primitive does **not** protect against,
83
+ * and for the warning about echoing failure detail to untrusted callers.
84
+ *
85
+ * @param url - The URL to fetch.
86
+ * @param options - Call options. `addressGuard` is required. `maxResponseBytes` defaults to
87
+ * 5 MiB and is meant to be tuned per call.
88
+ * @public
89
+ */
90
+ export declare function saferFetchJson(url: string | URL, options: ISaferFetchOptions): Promise<DetailedResult<ISaferFetchResponse<JsonValue>, FetchFailureReason>>;
91
+ /**
92
+ * Fetches a URL, parses the response body as JSON, and runs it through the supplied converter
93
+ * or validator so the caller reaches a validated `T` in one step.
94
+ *
95
+ * @remarks
96
+ * `T` is inferred from `converter` and is never caller-asserted, so the returned type is
97
+ * evidenced at runtime rather than claimed.
98
+ *
99
+ * See {@link SaferFetch.saferFetchBytes} for what this primitive does **not** protect against,
100
+ * and for the warning about echoing failure detail to untrusted callers.
101
+ *
102
+ * @param url - The URL to fetch.
103
+ * @param options - Call options plus the required `converter`. `addressGuard` is required.
104
+ * `maxResponseBytes` defaults to 5 MiB and is meant to be tuned per call.
105
+ * @public
106
+ */
107
+ export declare function saferFetchJson<T>(url: string | URL, options: ISaferFetchJsonOptions<T>): Promise<DetailedResult<ISaferFetchResponse<T>, FetchFailureReason>>;
108
+ //# sourceMappingURL=saferFetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"saferFetch.d.ts","sourceRoot":"","sources":["../../../src/packlets/safer-fetch/saferFetch.ts"],"names":[],"mappings":"AAoBA,OAAO,EAOL,KAAK,SAAS,EAEd,KAAK,cAAc,EAEnB,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAYrF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAW1D,OAAO,KAAK,EAIV,kBAAkB,EAElB,mBAAmB,EAIpB,MAAM,SAAS,CAAC;AAk+BjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,cAAc,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAE9E;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,cAAc,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAM1E;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC,CAAE,SAAQ,kBAAkB;IACnE,4FAA4F;IAC5F,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;CACjD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,cAAc,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAC/E;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,GAAG,EAAE,MAAM,GAAG,GAAG,EACjB,OAAO,EAAE,sBAAsB,CAAC,CAAC,CAAC,GACjC,OAAO,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC"}