@openclaw/discord 2026.6.33 → 2026.6.35

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 (179) hide show
  1. package/dist/action-runtime-api.js +1 -1
  2. package/dist/{api-CjnGBhM3.js → api-1GXlSrYP.js} +42 -21
  3. package/dist/api.js +14 -14
  4. package/dist/{approval-handler.runtime-BWPQrzpP.js → approval-handler.runtime-Dv4p7REU.js} +3 -3
  5. package/dist/{audit-BwJ_2OjL.js → audit-DEVi9nIs.js} +3 -3
  6. package/dist/{channel-Ddnonoy4.js → channel-CQIRiosn.js} +15 -15
  7. package/dist/{channel-actions-CwmIqnYN.js → channel-actions-JziPEcBU.js} +2 -2
  8. package/dist/{channel-actions.runtime-DvpTfaJ4.js → channel-actions.runtime-D7L47LY3.js} +5 -5
  9. package/dist/channel-plugin-api.js +1 -1
  10. package/dist/{channel.setup-glZ5Y98a.js → channel.setup-oMeR19Cc.js} +2 -2
  11. package/dist/{components-j6lTZBsT.js → components-_s5UPJqU.js} +1 -1
  12. package/dist/{conversation-identity-36FFBwm5.js → conversation-identity-B0k94-bz.js} +2 -2
  13. package/dist/{directory-config-BYMFzL29.js → directory-config-CD1svkyN.js} +1 -1
  14. package/dist/directory-contract-api.js +1 -1
  15. package/dist/{directory-live-9JtJCiu8.js → directory-live-DBTsSRxl.js} +5 -5
  16. package/dist/{handle-action.guild-admin-xxGYrp-I.js → handle-action.guild-admin-CYIFMxh-.js} +2 -2
  17. package/dist/index.js +1 -1
  18. package/dist/{manager.runtime-BK6cf2Zu.js → manager.runtime-BZVdGX2d.js} +17 -9
  19. package/dist/{message-handler-BqpJIavO.js → message-handler-DZd9Bpq8.js} +7 -6
  20. package/dist/{message-handler.preflight-C90MxCcv.js → message-handler.preflight-Axv9J9EO.js} +12 -11
  21. package/dist/{message-handler.process-CeQ7Bd_3.js → message-handler.process-CG0zFS-Z.js} +10 -10
  22. package/dist/{message-utils-CJeAoISH.js → message-utils-Dbuw27gd.js} +1 -1
  23. package/dist/{outbound-adapter-Dxs6iKEq.js → outbound-adapter-CKC_kmUN.js} +6 -6
  24. package/dist/pluralkit-CfTReLl3.js +40 -0
  25. package/dist/{probe-esE_Oqdm.js → probe-DcgZIpyF.js} +3 -2
  26. package/dist/{probe.runtime-C3Atgpvr.js → probe.runtime-CVwM9L5E.js} +1 -1
  27. package/dist/{provider-C7376XFH.js → provider-CwwSXIIj.js} +102 -37
  28. package/dist/{provider-session.runtime-CCdqogGf.js → provider-session.runtime-reBWkqHZ.js} +3 -3
  29. package/dist/provider.runtime-BsmTLYS_.js +2 -0
  30. package/dist/{resolve-allowlist-common-3gB2Gpmm.js → resolve-allowlist-common-D7bZqAw3.js} +3 -3
  31. package/dist/{resolve-channels-B3gQf8QL.js → resolve-channels--oE-RnO6.js} +6 -6
  32. package/dist/{resolve-users-BO10y5fJ.js → resolve-users-QXo8CXBU.js} +5 -5
  33. package/dist/{runtime-CFlR3L9X.js → runtime-CF7ramAs.js} +7 -7
  34. package/dist/runtime-api.actions.js +2 -2
  35. package/dist/runtime-api.js +19 -19
  36. package/dist/runtime-api.lookup.js +5 -5
  37. package/dist/runtime-api.monitor-CzF8MUl_.js +6 -0
  38. package/dist/runtime-api.monitor.js +5 -5
  39. package/dist/runtime-api.send.js +5 -5
  40. package/dist/runtime-api.threads.js +3 -3
  41. package/dist/{send-JHxfT4Pk.js → send-Bb295iHT.js} +5 -5
  42. package/dist/{send.components-CN35E2Ck.js → send.components-COkDUO3B.js} +4 -4
  43. package/dist/{send.outbound-B-cA1Esv.js → send.outbound-B_sk0hNe.js} +3 -3
  44. package/dist/{send.receipt-CGecJDin.js → send.receipt-Cli6_9Sn.js} +19 -7
  45. package/dist/{send.shared-vcbfThdy.js → send.shared-BCG7MWnP.js} +2 -2
  46. package/dist/session-binding-contract-api.js +1 -1
  47. package/dist/setup-plugin-api.js +1 -1
  48. package/dist/{subagent-hooks-20wXhVzZ.js → subagent-hooks-CtqoWK4x.js} +2 -2
  49. package/dist/subagent-hooks-api.js +1 -1
  50. package/dist/{system-events-DR27TE7p.js → system-events-BxGNk_wE.js} +1 -1
  51. package/dist/{target-resolver-CDi1gI90.js → target-resolver-BcoK6b0g.js} +2 -2
  52. package/dist/targets-Q-ZoP7K4.js +3 -0
  53. package/dist/{thread-bindings-DU4T6nDo.js → thread-bindings-DJ_HT6GJ.js} +4 -4
  54. package/dist/{thread-bindings.discord-api-CYkr-3Gj.js → thread-bindings.discord-api-OPyjvgyz.js} +4 -4
  55. package/dist/{thread-bindings.manager-BqWs1UE2.js → thread-bindings.manager-Cd2HAVw_.js} +3 -3
  56. package/dist/{transcripts-source-D-lBI5ZD.js → transcripts-source-CPf5gURy.js} +1 -1
  57. package/dist/transcripts-source-api.js +1 -1
  58. package/dist/{typing-xr05bL-r.js → typing-DY0cwLdg.js} +1 -1
  59. package/node_modules/undici/README.md +8 -2
  60. package/node_modules/undici/docs/docs/api/Agent.md +248 -44
  61. package/node_modules/undici/docs/docs/api/BalancedPool.md +246 -48
  62. package/node_modules/undici/docs/docs/api/CacheStorage.md +171 -13
  63. package/node_modules/undici/docs/docs/api/CacheStore.md +294 -98
  64. package/node_modules/undici/docs/docs/api/Client.md +386 -179
  65. package/node_modules/undici/docs/docs/api/ClientStats.md +80 -14
  66. package/node_modules/undici/docs/docs/api/Connector.md +118 -36
  67. package/node_modules/undici/docs/docs/api/ContentType.md +65 -25
  68. package/node_modules/undici/docs/docs/api/Cookies.md +122 -71
  69. package/node_modules/undici/docs/docs/api/Debug.md +34 -18
  70. package/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +364 -164
  71. package/node_modules/undici/docs/docs/api/Dispatcher.md +485 -1102
  72. package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +102 -87
  73. package/node_modules/undici/docs/docs/api/Errors.md +503 -40
  74. package/node_modules/undici/docs/docs/api/EventSource.md +298 -32
  75. package/node_modules/undici/docs/docs/api/Fetch.md +680 -31
  76. package/node_modules/undici/docs/docs/api/GlobalInstallation.md +78 -98
  77. package/node_modules/undici/docs/docs/api/H2CClient.md +298 -176
  78. package/node_modules/undici/docs/docs/api/Interceptors.md +418 -0
  79. package/node_modules/undici/docs/docs/api/MockAgent.md +333 -314
  80. package/node_modules/undici/docs/docs/api/MockCallHistory.md +272 -98
  81. package/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +189 -26
  82. package/node_modules/undici/docs/docs/api/MockClient.md +151 -33
  83. package/node_modules/undici/docs/docs/api/MockErrors.md +56 -5
  84. package/node_modules/undici/docs/docs/api/MockPool.md +321 -258
  85. package/node_modules/undici/docs/docs/api/Pool.md +235 -43
  86. package/node_modules/undici/docs/docs/api/PoolStats.md +119 -13
  87. package/node_modules/undici/docs/docs/api/ProxyAgent.md +179 -160
  88. package/node_modules/undici/docs/docs/api/RedirectHandler.md +238 -63
  89. package/node_modules/undici/docs/docs/api/RetryAgent.md +113 -26
  90. package/node_modules/undici/docs/docs/api/RetryHandler.md +161 -77
  91. package/node_modules/undici/docs/docs/api/RoundRobinPool.md +166 -72
  92. package/node_modules/undici/docs/docs/api/SnapshotAgent.md +264 -542
  93. package/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +177 -196
  94. package/node_modules/undici/docs/docs/api/Util.md +68 -11
  95. package/node_modules/undici/docs/docs/api/WebSocket.md +475 -80
  96. package/node_modules/undici/docs/docs/api/api-lifecycle.md +100 -32
  97. package/node_modules/undici/docs/docs/best-practices/client-certificate.md +2 -0
  98. package/node_modules/undici/docs/docs/best-practices/crawling.md +3 -1
  99. package/node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md +6 -4
  100. package/node_modules/undici/docs/docs/best-practices/mocking-request.md +9 -7
  101. package/node_modules/undici/docs/docs/best-practices/proxy.md +3 -1
  102. package/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +10 -8
  103. package/node_modules/undici/docs/docs/best-practices/writing-tests.md +2 -0
  104. package/node_modules/undici/docs/docs/{GettingStarted.md → getting-started.md} +22 -19
  105. package/node_modules/undici/docs/docs/index.md +784 -0
  106. package/node_modules/undici/docs/docs/site.json +125 -0
  107. package/node_modules/undici/docs/docs/type-map.json +79 -0
  108. package/node_modules/undici/lib/api/api-request.js +7 -1
  109. package/node_modules/undici/lib/api/readable.js +42 -7
  110. package/node_modules/undici/lib/cache/memory-cache-store.js +56 -11
  111. package/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
  112. package/node_modules/undici/lib/core/connect.js +16 -2
  113. package/node_modules/undici/lib/core/errors.js +20 -0
  114. package/node_modules/undici/lib/core/request.js +14 -3
  115. package/node_modules/undici/lib/core/symbols.js +3 -0
  116. package/node_modules/undici/lib/core/util.js +30 -2
  117. package/node_modules/undici/lib/dispatcher/agent.js +10 -5
  118. package/node_modules/undici/lib/dispatcher/balanced-pool.js +7 -4
  119. package/node_modules/undici/lib/dispatcher/client-h1.js +74 -9
  120. package/node_modules/undici/lib/dispatcher/client-h2.js +483 -151
  121. package/node_modules/undici/lib/dispatcher/client.js +76 -30
  122. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +13 -1
  123. package/node_modules/undici/lib/dispatcher/dispatcher.js +15 -1
  124. package/node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js +3 -0
  125. package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +37 -8
  126. package/node_modules/undici/lib/dispatcher/proxy-agent.js +58 -7
  127. package/node_modules/undici/lib/dispatcher/retry-agent.js +3 -0
  128. package/node_modules/undici/lib/dispatcher/round-robin-pool.js +1 -1
  129. package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +106 -22
  130. package/node_modules/undici/lib/global.js +36 -16
  131. package/node_modules/undici/lib/handler/cache-handler.js +297 -65
  132. package/node_modules/undici/lib/handler/cache-revalidation-handler.js +13 -3
  133. package/node_modules/undici/lib/handler/decorator-handler.js +7 -1
  134. package/node_modules/undici/lib/handler/deduplication-handler.js +12 -6
  135. package/node_modules/undici/lib/handler/redirect-handler.js +26 -5
  136. package/node_modules/undici/lib/handler/retry-handler.js +238 -51
  137. package/node_modules/undici/lib/interceptor/cache.js +221 -58
  138. package/node_modules/undici/lib/interceptor/decompress.js +124 -15
  139. package/node_modules/undici/lib/interceptor/deduplicate.js +5 -4
  140. package/node_modules/undici/lib/interceptor/dns.js +7 -0
  141. package/node_modules/undici/lib/interceptor/dump.js +10 -23
  142. package/node_modules/undici/lib/mock/mock-agent.js +13 -1
  143. package/node_modules/undici/lib/mock/mock-interceptor.js +25 -7
  144. package/node_modules/undici/lib/mock/mock-utils.js +246 -27
  145. package/node_modules/undici/lib/util/cache.js +415 -71
  146. package/node_modules/undici/lib/util/date.js +23 -6
  147. package/node_modules/undici/lib/web/cookies/parse.js +150 -146
  148. package/node_modules/undici/lib/web/cookies/util.js +79 -9
  149. package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +28 -1
  150. package/node_modules/undici/lib/web/eventsource/eventsource.js +13 -3
  151. package/node_modules/undici/lib/web/eventsource/util.js +1 -1
  152. package/node_modules/undici/lib/web/fetch/body.js +2 -1
  153. package/node_modules/undici/lib/web/fetch/constants.js +1 -1
  154. package/node_modules/undici/lib/web/fetch/index.js +17 -2
  155. package/node_modules/undici/lib/web/fetch/request.js +2 -3
  156. package/node_modules/undici/lib/web/fetch/response.js +1 -3
  157. package/node_modules/undici/lib/web/fetch/util.js +10 -2
  158. package/node_modules/undici/lib/web/webidl/index.js +3 -3
  159. package/node_modules/undici/lib/web/websocket/connection.js +1 -1
  160. package/node_modules/undici/lib/web/websocket/permessage-deflate.js +5 -0
  161. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +8 -10
  162. package/node_modules/undici/lib/web/websocket/websocket.js +22 -0
  163. package/node_modules/undici/package.json +4 -4
  164. package/node_modules/undici/types/client.d.ts +53 -3
  165. package/node_modules/undici/types/cookies.d.ts +1 -1
  166. package/node_modules/undici/types/dispatcher.d.ts +3 -3
  167. package/node_modules/undici/types/errors.d.ts +10 -0
  168. package/node_modules/undici/types/handlers.d.ts +2 -0
  169. package/node_modules/undici/types/interceptors.d.ts +2 -0
  170. package/node_modules/undici/types/mock-agent.d.ts +11 -10
  171. package/node_modules/undici/types/mock-interceptor.d.ts +5 -1
  172. package/node_modules/undici/types/proxy-agent.d.ts +7 -0
  173. package/node_modules/undici/types/socks5-proxy-agent.d.ts +2 -0
  174. package/npm-shrinkwrap.json +7 -7
  175. package/package.json +6 -5
  176. package/dist/pluralkit-DI6RYaVj.js +0 -24
  177. package/dist/provider.runtime-D1m66kDS.js +0 -2
  178. package/dist/runtime-api.monitor-4oc58TSk.js +0 -6
  179. package/dist/targets-C6yd3y8R.js +0 -3
@@ -3,7 +3,10 @@
3
3
  const util = require('../core/util')
4
4
  const {
5
5
  parseCacheControlHeader,
6
+ hasInvalidCacheControlDirective,
6
7
  parseVaryHeader,
8
+ hasVaryStar,
9
+ isInvalidOrWildcardVaryHeader,
7
10
  isEtagUsable
8
11
  } = require('../util/cache')
9
12
  const { parseHttpDate } = require('../util/date.js')
@@ -26,6 +29,95 @@ const NOT_UNDERSTOOD_STATUS_CODES = [
26
29
 
27
30
  const MAX_RESPONSE_AGE = 2147483647000
28
31
 
32
+ // Retention for revalidation-only entries (zero freshness lifetime but a
33
+ // validator present); each successful revalidation re-stores the entry.
34
+ const REVALIDATION_ONLY_RETENTION = 86400000 // 24 hours
35
+
36
+ function trimOWS (value) {
37
+ return value.replace(/^[\t ]+|[\t ]+$/g, '')
38
+ }
39
+
40
+ function arrayIncludes (array, value) {
41
+ for (let i = 0; i < array.length; i++) {
42
+ if (array[i] === value) {
43
+ return true
44
+ }
45
+ }
46
+
47
+ return false
48
+ }
49
+
50
+ function appendConnectionHeaderTokens (headersToRemove, connectionHeader) {
51
+ const values = Array.isArray(connectionHeader) ? connectionHeader : [connectionHeader]
52
+
53
+ for (let i = 0; i < values.length; i++) {
54
+ const tokens = values[i].split(',')
55
+ for (let j = 0; j < tokens.length; j++) {
56
+ headersToRemove.push(trimOWS(tokens[j]).toLowerCase())
57
+ }
58
+ }
59
+ }
60
+
61
+ function getSameOriginPath (cacheKey, location) {
62
+ if (typeof location !== 'string') {
63
+ return undefined
64
+ }
65
+
66
+ let originUrl
67
+ let requestUrl
68
+ let locationUrl
69
+ try {
70
+ originUrl = new URL(cacheKey.origin)
71
+ requestUrl = new URL(cacheKey.path, originUrl)
72
+ locationUrl = new URL(location, requestUrl)
73
+ } catch {
74
+ return undefined
75
+ }
76
+
77
+ if (locationUrl.origin !== originUrl.origin) {
78
+ return undefined
79
+ }
80
+
81
+ return locationUrl.pathname + locationUrl.search
82
+ }
83
+
84
+ function deleteCachedUri (store, cacheKey, path) {
85
+ deleteCachedValue(store, {
86
+ ...cacheKey,
87
+ path
88
+ })
89
+
90
+ for (let i = 0; i < util.safeHTTPMethods.length; i++) {
91
+ const method = util.safeHTTPMethods[i]
92
+ if (method !== cacheKey.method) {
93
+ deleteCachedValue(store, {
94
+ ...cacheKey,
95
+ method,
96
+ path
97
+ })
98
+ }
99
+ }
100
+ }
101
+
102
+ function deleteLocationTargets (store, cacheKey, headerValue) {
103
+ if (headerValue === undefined) {
104
+ return
105
+ }
106
+
107
+ const values = Array.isArray(headerValue) ? headerValue : [headerValue]
108
+ for (let i = 0; i < values.length; i++) {
109
+ const path = getSameOriginPath(cacheKey, values[i])
110
+ if (path !== undefined) {
111
+ deleteCachedUri(store, cacheKey, path)
112
+ }
113
+ }
114
+ }
115
+
116
+ function invalidateUnsafeRequest (store, cacheKey, resHeaders) {
117
+ deleteCachedUri(store, cacheKey, cacheKey.path)
118
+ deleteLocationTargets(store, cacheKey, resHeaders.location)
119
+ deleteLocationTargets(store, cacheKey, resHeaders['content-location'])
120
+ }
29
121
  /**
30
122
  * @typedef {import('../../types/dispatcher.d.ts').default.DispatchHandler} DispatchHandler
31
123
  *
@@ -81,6 +173,14 @@ class CacheHandler {
81
173
  this.#handler.onRequestStart?.(controller, context)
82
174
  }
83
175
 
176
+ onBodySent (chunk) {
177
+ this.#handler.onBodySent?.(chunk)
178
+ }
179
+
180
+ onRequestSent () {
181
+ this.#handler.onRequestSent?.()
182
+ }
183
+
84
184
  onRequestUpgrade (controller, statusCode, headers, socket) {
85
185
  this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
86
186
  }
@@ -107,60 +207,89 @@ class CacheHandler {
107
207
  const handler = this
108
208
 
109
209
  if (
110
- !util.safeHTTPMethods.includes(this.#cacheKey.method) &&
210
+ !arrayIncludes(util.safeHTTPMethods, this.#cacheKey.method) &&
111
211
  statusCode >= 200 &&
112
212
  statusCode <= 399
113
213
  ) {
114
214
  // Successful response to an unsafe method, delete it from cache
115
215
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-invalidating-stored-response
116
- try {
117
- this.#store.delete(this.#cacheKey)?.catch?.(noop)
118
- } catch {
119
- // Fail silently
120
- }
216
+ invalidateUnsafeRequest(this.#store, this.#cacheKey, resHeaders)
121
217
  return downstreamOnHeaders()
122
218
  }
123
219
 
124
220
  const cacheControlHeader = resHeaders['cache-control']
125
- const heuristicallyCacheable = resHeaders['last-modified'] && HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode)
221
+ const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
222
+
223
+ if (revalidationResponseDisallowsCachedReuse(this.#cacheType, resHeaders, cacheControlDirectives)) {
224
+ deleteCachedValue(this.#store, this.#cacheKey)
225
+ return downstreamOnHeaders()
226
+ }
227
+
228
+ const heuristicallyCacheable = resHeaders['last-modified'] && arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode)
126
229
  if (
127
230
  !cacheControlHeader &&
128
231
  !resHeaders['expires'] &&
129
232
  !heuristicallyCacheable &&
130
233
  !this.#cacheByDefault
131
234
  ) {
235
+ if (statusCode === 304 && resHeaders.vary && isInvalidOrWildcardVaryHeader(resHeaders.vary)) {
236
+ deleteCachedValue(this.#store, this.#cacheKey)
237
+ }
238
+
132
239
  // Don't have anything to tell us this response is cachable and we're not
133
240
  // caching by default
134
241
  return downstreamOnHeaders()
135
242
  }
136
243
 
137
- const cacheControlDirectives = cacheControlHeader ? parseCacheControlHeader(cacheControlHeader) : {}
138
- if (!canCacheResponse(this.#cacheType, statusCode, resHeaders, cacheControlDirectives, this.#cacheKey.headers)) {
244
+ if (!canCacheResponse(this.#cacheType, this.#cacheKey.method, statusCode, resHeaders, cacheControlDirectives, this.#cacheKey.headers)) {
245
+ if (statusCode === 304 && (cacheControlHeader || revalidationResponseDisallowsCachedReuse(this.#cacheType, resHeaders, cacheControlDirectives))) {
246
+ deleteCachedValue(this.#store, this.#cacheKey)
247
+ }
248
+
139
249
  return downstreamOnHeaders()
140
250
  }
141
251
 
142
252
  const now = Date.now()
143
- const resAge = resHeaders.age ? getAge(resHeaders.age) : undefined
144
- if (resAge && resAge >= MAX_RESPONSE_AGE) {
253
+ const resAge = Object.hasOwn(resHeaders, 'age') ? getAge(resHeaders.age) : undefined
254
+ if (resAge !== undefined && resAge >= MAX_RESPONSE_AGE) {
145
255
  // Response considered stale
256
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
146
257
  return downstreamOnHeaders()
147
258
  }
148
259
 
149
- const resDate = typeof resHeaders.date === 'string'
150
- ? parseHttpDate(resHeaders.date)
151
- : undefined
260
+ const resDate = Object.hasOwn(resHeaders, 'date') ? getDate(resHeaders.date) : undefined
261
+ if (resDate === null) {
262
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
263
+ return downstreamOnHeaders()
264
+ }
265
+
266
+ const apparentAge = resDate ? Math.max(0, now - resDate.getTime()) : 0
267
+ const currentAge = Math.max(apparentAge, resAge ?? 0)
268
+
269
+ const hasValidator =
270
+ (typeof resHeaders.etag === 'string' && isEtagUsable(resHeaders.etag)) ||
271
+ typeof resHeaders['last-modified'] === 'string'
152
272
 
153
273
  const staleAt =
154
- determineStaleAt(this.#cacheType, now, resAge, resHeaders, resDate, cacheControlDirectives) ??
274
+ determineStaleAt(this.#cacheType, now, resAge, resHeaders, resDate, cacheControlDirectives, hasValidator) ??
155
275
  this.#cacheByDefault
156
- if (staleAt === undefined || (resAge && resAge > staleAt)) {
276
+ // Zero freshness lifetime but a validator: stale from the start, yet still
277
+ // storable since each reuse is preceded by a revalidation request.
278
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.4
279
+ const revalidationOnly = staleAt === 0 && hasValidator
280
+ if (staleAt === undefined || (currentAge >= staleAt && !revalidationOnly)) {
281
+ if (cacheControlHeader || staleAt !== undefined) {
282
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
283
+ }
284
+
157
285
  return downstreamOnHeaders()
158
286
  }
159
287
 
160
- const baseTime = resDate ? resDate.getTime() : now
288
+ const baseTime = now - currentAge
161
289
  const absoluteStaleAt = staleAt + baseTime
162
- if (now >= absoluteStaleAt) {
290
+ if (now >= absoluteStaleAt && !revalidationOnly) {
163
291
  // Response is already stale
292
+ deleteCachedValueIfNotModified(statusCode, this.#store, this.#cacheKey)
164
293
  return downstreamOnHeaders()
165
294
  }
166
295
 
@@ -173,8 +302,8 @@ class CacheHandler {
173
302
  }
174
303
  }
175
304
 
176
- const cachedAt = resAge ? now - resAge : now
177
- const deleteAt = determineDeleteAt(baseTime, cachedAt, cacheControlDirectives, absoluteStaleAt)
305
+ const cachedAt = baseTime
306
+ const deleteAt = determineDeleteAt(baseTime, now, cacheControlDirectives, absoluteStaleAt)
178
307
  const strippedHeaders = stripNecessaryHeaders(resHeaders, cacheControlDirectives)
179
308
 
180
309
  /**
@@ -204,6 +333,7 @@ class CacheHandler {
204
333
  value.statusCode = cachedValue.statusCode
205
334
  value.statusMessage = cachedValue.statusMessage
206
335
  value.etag = cachedValue.etag
336
+ value.vary = varyDirectives ?? cachedValue.vary
207
337
  value.headers = { ...cachedValue.headers, ...strippedHeaders }
208
338
 
209
339
  downstreamOnHeaders()
@@ -334,23 +464,60 @@ class CacheHandler {
334
464
  }
335
465
  }
336
466
 
467
+ /**
468
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheStore} store
469
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheKey} cacheKey
470
+ */
471
+ function deleteCachedValue (store, cacheKey) {
472
+ try {
473
+ store.delete(cacheKey)?.catch?.(noop)
474
+ } catch {
475
+ // Fail silently
476
+ }
477
+ }
478
+
479
+ function deleteCachedValueIfNotModified (statusCode, store, cacheKey) {
480
+ if (statusCode === 304) {
481
+ deleteCachedValue(store, cacheKey)
482
+ }
483
+ }
484
+
485
+ /**
486
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
487
+ * @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
488
+ * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
489
+ * @returns {boolean}
490
+ */
491
+ function revalidationResponseDisallowsCachedReuse (cacheType, resHeaders, cacheControlDirectives) {
492
+ return cacheControlDirectives['no-store'] === true ||
493
+ (cacheType === 'shared' && (
494
+ cacheControlDirectives.private === true ||
495
+ Object.hasOwn(resHeaders, 'set-cookie')
496
+ )) ||
497
+ (resHeaders.vary ? isInvalidOrWildcardVaryHeader(resHeaders.vary) : false)
498
+ }
499
+
337
500
  /**
338
501
  * @see https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
339
502
  *
340
503
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheOptions['type']} cacheType
504
+ * @param {string} method
341
505
  * @param {number} statusCode
342
506
  * @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
343
507
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
344
508
  * @param {import('../../types/header.d.ts').IncomingHttpHeaders} [reqHeaders]
345
509
  */
346
- function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirectives, reqHeaders) {
510
+ function canCacheResponse (cacheType, method, statusCode, resHeaders, cacheControlDirectives, reqHeaders) {
511
+ if (!arrayIncludes(util.safeHTTPMethods, method)) {
512
+ return false
513
+ }
347
514
  // Status code must be final and understood.
348
- if (statusCode < 200 || NOT_UNDERSTOOD_STATUS_CODES.includes(statusCode)) {
515
+ if (statusCode < 200 || arrayIncludes(NOT_UNDERSTOOD_STATUS_CODES, statusCode)) {
349
516
  return false
350
517
  }
351
518
  // Responses with neither status codes that are heuristically cacheable, nor "explicit enough" caching
352
519
  // directives, are not cacheable. "Explicit enough": see https://www.rfc-editor.org/rfc/rfc9111.html#section-3
353
- if (!HEURISTICALLY_CACHEABLE_STATUS_CODES.includes(statusCode) && !resHeaders['expires'] &&
520
+ if (!arrayIncludes(HEURISTICALLY_CACHEABLE_STATUS_CODES, statusCode) && !resHeaders['expires'] &&
354
521
  !cacheControlDirectives.public &&
355
522
  cacheControlDirectives['max-age'] === undefined &&
356
523
  // RFC 9111: a private response directive, if the cache is not shared
@@ -364,17 +531,20 @@ function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirect
364
531
  return false
365
532
  }
366
533
 
367
- if (cacheType === 'shared' && cacheControlDirectives.private === true) {
534
+ if (cacheType === 'shared' && (
535
+ cacheControlDirectives.private === true ||
536
+ Object.hasOwn(resHeaders, 'set-cookie')
537
+ )) {
368
538
  return false
369
539
  }
370
540
 
371
541
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-4.1-5
372
- if (resHeaders.vary?.includes('*')) {
542
+ if (resHeaders.vary && hasVaryStar(resHeaders.vary)) {
373
543
  return false
374
544
  }
375
545
 
376
546
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-storing-responses-to-authen
377
- if (reqHeaders?.authorization) {
547
+ if (reqHeaders != null && Object.hasOwn(reqHeaders, 'authorization')) {
378
548
  if (
379
549
  !cacheControlDirectives.public &&
380
550
  !cacheControlDirectives['s-maxage'] &&
@@ -389,14 +559,14 @@ function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirect
389
559
 
390
560
  if (
391
561
  Array.isArray(cacheControlDirectives['no-cache']) &&
392
- cacheControlDirectives['no-cache'].includes('authorization')
562
+ arrayIncludes(cacheControlDirectives['no-cache'], 'authorization')
393
563
  ) {
394
564
  return false
395
565
  }
396
566
 
397
567
  if (
398
568
  Array.isArray(cacheControlDirectives['private']) &&
399
- cacheControlDirectives['private'].includes('authorization')
569
+ arrayIncludes(cacheControlDirectives['private'], 'authorization')
400
570
  ) {
401
571
  return false
402
572
  }
@@ -405,14 +575,51 @@ function canCacheResponse (cacheType, statusCode, resHeaders, cacheControlDirect
405
575
  return true
406
576
  }
407
577
 
578
+ /**
579
+ * @param {string | string[]} dateHeader
580
+ * @returns {Date | null | undefined}
581
+ */
582
+ function getDate (dateHeader) {
583
+ let dateValue = dateHeader
584
+ if (Array.isArray(dateValue)) {
585
+ if (dateValue.length !== 1) {
586
+ return null
587
+ }
588
+
589
+ dateValue = dateValue[0]
590
+ }
591
+
592
+ if (typeof dateValue !== 'string') {
593
+ return null
594
+ }
595
+
596
+ return parseHttpDate(dateValue)
597
+ }
598
+
408
599
  /**
409
600
  * @param {string | string[]} ageHeader
410
601
  * @returns {number | undefined}
411
602
  */
412
603
  function getAge (ageHeader) {
413
- const age = parseInt(Array.isArray(ageHeader) ? ageHeader[0] : ageHeader)
604
+ let ageValue = ageHeader
605
+ if (Array.isArray(ageValue)) {
606
+ if (ageValue.length !== 1) {
607
+ return MAX_RESPONSE_AGE
608
+ }
609
+
610
+ ageValue = ageValue[0]
611
+ }
612
+
613
+ if (typeof ageValue !== 'string' || !/^[\t ]*[0-9]+[\t ]*$/.test(ageValue)) {
614
+ return MAX_RESPONSE_AGE
615
+ }
616
+
617
+ const age = BigInt(ageValue.replace(/^[\t ]+|[\t ]+$/g, ''))
618
+ if (age >= BigInt(MAX_RESPONSE_AGE / 1000)) {
619
+ return MAX_RESPONSE_AGE
620
+ }
414
621
 
415
- return isNaN(age) ? undefined : age * 1000
622
+ return Number(age) * 1000
416
623
  }
417
624
 
418
625
  /**
@@ -422,51 +629,80 @@ function getAge (ageHeader) {
422
629
  * @param {import('../../types/header.d.ts').IncomingHttpHeaders} resHeaders
423
630
  * @param {Date | undefined} responseDate
424
631
  * @param {import('../../types/cache-interceptor.d.ts').default.CacheControlDirectives} cacheControlDirectives
632
+ * @param {boolean} hasValidator whether the response has a validator (etag or
633
+ * last-modified) that revalidation requests can be made with
425
634
  *
426
635
  * @returns {number | undefined} time that the value is stale at in seconds or undefined if it shouldn't be cached
427
636
  */
428
- function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheControlDirectives) {
637
+ function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheControlDirectives, hasValidator) {
429
638
  if (cacheType === 'shared') {
430
639
  // Prioritize s-maxage since we're a shared cache
431
640
  // s-maxage > max-age > Expire
432
641
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.10-3
642
+ if (hasInvalidCacheControlDirective(cacheControlDirectives, 's-maxage')) {
643
+ return 0
644
+ }
645
+
433
646
  const sMaxAge = cacheControlDirectives['s-maxage']
434
647
  if (sMaxAge !== undefined) {
435
- return sMaxAge > 0 ? sMaxAge * 1000 : undefined
648
+ if (sMaxAge > 0) {
649
+ return sMaxAge * 1000
650
+ }
651
+
652
+ // Immediately stale, but storable if we can revalidate it before reuse.
653
+ return 0
436
654
  }
437
655
  }
438
656
 
657
+ if (hasInvalidCacheControlDirective(cacheControlDirectives, 'max-age')) {
658
+ return 0
659
+ }
660
+
439
661
  const maxAge = cacheControlDirectives['max-age']
440
662
  if (maxAge !== undefined) {
441
- return maxAge > 0 ? maxAge * 1000 : undefined
663
+ if (maxAge > 0) {
664
+ return maxAge * 1000
665
+ }
666
+
667
+ // Immediately stale, but storable if we can revalidate it before reuse.
668
+ return 0
442
669
  }
443
670
 
444
- if (typeof resHeaders.expires === 'string') {
671
+ if (Object.hasOwn(resHeaders, 'expires')) {
445
672
  // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.3
673
+ if (typeof resHeaders.expires !== 'string') {
674
+ return 0
675
+ }
676
+
446
677
  const expiresDate = parseHttpDate(resHeaders.expires)
447
- if (expiresDate) {
448
- if (now >= expiresDate.getTime()) {
449
- return undefined
450
- }
678
+ if (!expiresDate) {
679
+ return 0
680
+ }
451
681
 
452
- if (responseDate) {
453
- if (responseDate >= expiresDate) {
454
- return undefined
455
- }
682
+ if (now >= expiresDate.getTime()) {
683
+ return 0
684
+ }
456
685
 
457
- if (age !== undefined && age > (expiresDate - responseDate)) {
458
- return undefined
459
- }
686
+ if (responseDate) {
687
+ if (responseDate >= expiresDate) {
688
+ return 0
689
+ }
690
+
691
+ const freshnessLifetime = expiresDate.getTime() - responseDate.getTime()
692
+ if (age !== undefined && age >= freshnessLifetime) {
693
+ return 0
460
694
  }
461
695
 
462
- return expiresDate.getTime() - now
696
+ return freshnessLifetime
463
697
  }
698
+
699
+ return expiresDate.getTime() - now
464
700
  }
465
701
 
466
702
  if (typeof resHeaders['last-modified'] === 'string') {
467
703
  // https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-heuristic-fresh
468
- const lastModified = new Date(resHeaders['last-modified'])
469
- if (isValidDate(lastModified)) {
704
+ const lastModified = parseHttpDate(resHeaders['last-modified'])
705
+ if (lastModified) {
470
706
  if (lastModified.getTime() >= now) {
471
707
  return undefined
472
708
  }
@@ -482,6 +718,12 @@ function determineStaleAt (cacheType, now, age, resHeaders, responseDate, cacheC
482
718
  return 31536000000
483
719
  }
484
720
 
721
+ if (cacheControlDirectives['no-cache'] === true && hasValidator) {
722
+ // No freshness source, but a validator lets us revalidate before reuse.
723
+ // https://www.rfc-editor.org/rfc/rfc9111.html#section-5.2.2.4
724
+ return 0
725
+ }
726
+
485
727
  return undefined
486
728
  }
487
729
 
@@ -518,6 +760,11 @@ function determineDeleteAt (baseTime, cachedAt, cacheControlDirectives, staleAt)
518
760
  // revalidated.
519
761
  if (staleWhileRevalidate === -Infinity && staleIfError === -Infinity && immutable === -Infinity) {
520
762
  const freshnessLifetime = staleAt - baseTime
763
+ if (freshnessLifetime <= 0) {
764
+ // Revalidation-only entry: no freshness lifetime to size the buffer on,
765
+ // so retain it for a bounded window instead.
766
+ return cachedAt + REVALIDATION_ONLY_RETENTION
767
+ }
521
768
  const datePrecisionPadding = Math.min(Math.max(cachedAt - baseTime, 0), 1000)
522
769
  return staleAt + freshnessLifetime + datePrecisionPadding
523
770
  }
@@ -546,14 +793,7 @@ function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
546
793
  ]
547
794
 
548
795
  if (resHeaders['connection']) {
549
- if (Array.isArray(resHeaders['connection'])) {
550
- // connection: a
551
- // connection: b
552
- headersToRemove.push(...resHeaders['connection'].map(header => header.trim()))
553
- } else {
554
- // connection: a, b
555
- headersToRemove.push(...resHeaders['connection'].split(',').map(header => header.trim()))
556
- }
796
+ appendConnectionHeaderTokens(headersToRemove, resHeaders['connection'])
557
797
  }
558
798
 
559
799
  if (Array.isArray(cacheControlDirectives['no-cache'])) {
@@ -566,7 +806,7 @@ function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
566
806
 
567
807
  let strippedHeaders
568
808
  for (const headerName of headersToRemove) {
569
- if (resHeaders[headerName]) {
809
+ if (Object.hasOwn(resHeaders, headerName)) {
570
810
  strippedHeaders ??= { ...resHeaders }
571
811
  delete strippedHeaders[headerName]
572
812
  }
@@ -575,12 +815,4 @@ function stripNecessaryHeaders (resHeaders, cacheControlDirectives) {
575
815
  return strippedHeaders ?? resHeaders
576
816
  }
577
817
 
578
- /**
579
- * @param {Date} date
580
- * @returns {boolean}
581
- */
582
- function isValidDate (date) {
583
- return date instanceof Date && Number.isFinite(date.valueOf())
584
- }
585
-
586
818
  module.exports = CacheHandler
@@ -19,7 +19,7 @@ class CacheRevalidationHandler {
19
19
  #successful = false
20
20
 
21
21
  /**
22
- * @type {((boolean, any) => void) | null}
22
+ * @type {((success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void) | null}
23
23
  */
24
24
  #callback
25
25
 
@@ -36,7 +36,7 @@ class CacheRevalidationHandler {
36
36
  #allowErrorStatusCodes
37
37
 
38
38
  /**
39
- * @param {(boolean) => void} callback Function to call if the cached value is valid
39
+ * @param {(success: boolean, context?: any, statusCode?: number, headers?: import('../../types/header.d.ts').IncomingHttpHeaders) => void} callback Function to call if the cached value is valid
40
40
  * @param {import('../../types/dispatcher.d.ts').default.DispatchHandlers} handler
41
41
  * @param {boolean} allowErrorStatusCodes
42
42
  */
@@ -71,7 +71,7 @@ class CacheRevalidationHandler {
71
71
  // https://datatracker.ietf.org/doc/html/rfc5861#section-4
72
72
  this.#successful = statusCode === 304 ||
73
73
  (this.#allowErrorStatusCodes && statusCode >= 500 && statusCode <= 504)
74
- this.#callback(this.#successful, this.#context)
74
+ this.#callback(this.#successful, this.#context, statusCode, headers)
75
75
  this.#callback = null
76
76
 
77
77
  if (this.#successful) {
@@ -109,6 +109,16 @@ class CacheRevalidationHandler {
109
109
  }
110
110
 
111
111
  if (this.#callback) {
112
+ // Serve the stale cached response on a connection error, per stale-if-error:
113
+ // RFC 5861 counts an unreachable origin (a would-be 5xx) as an error.
114
+ // https://datatracker.ietf.org/doc/html/rfc5861#section-4
115
+ if (this.#allowErrorStatusCodes) {
116
+ this.#successful = true
117
+ this.#callback(true, this.#context)
118
+ this.#callback = null
119
+ return
120
+ }
121
+
112
122
  this.#callback(false)
113
123
  this.#callback = null
114
124
  }
@@ -62,5 +62,11 @@ module.exports = class DecoratorHandler {
62
62
  /**
63
63
  * @deprecated
64
64
  */
65
- onBodySent () {}
65
+ onBodySent (...args) {
66
+ return this.#handler.onBodySent?.(...args)
67
+ }
68
+
69
+ onRequestSent (...args) {
70
+ return this.#handler.onRequestSent?.(...args)
71
+ }
66
72
  }
@@ -365,12 +365,22 @@ class DeduplicationHandler {
365
365
  get aborted () { return state.aborted },
366
366
  get reason () { return state.reason },
367
367
  abort: (reason) => {
368
+ if (state.aborted) {
369
+ return
370
+ }
371
+
368
372
  state.aborted = true
369
373
  state.reason = reason ?? null
370
374
  waitingHandler.done = true
371
375
  waitingHandler.pendingTrailers = null
372
376
  waitingHandler.bufferedChunks = []
373
377
  waitingHandler.bufferedBytes = 0
378
+
379
+ try {
380
+ handler.onResponseError?.(waitingHandler.controller, state.reason ?? new RequestAbortedError())
381
+ } catch {
382
+ // Ignore errors from waiting handlers
383
+ }
374
384
  }
375
385
  }
376
386
 
@@ -444,12 +454,8 @@ class DeduplicationHandler {
444
454
  waitingHandler.bufferedChunks = []
445
455
  waitingHandler.bufferedBytes = 0
446
456
 
447
- try {
448
- waitingHandler.controller.abort(err)
449
- waitingHandler.handler.onResponseError?.(waitingHandler.controller, err)
450
- } catch {
451
- // Ignore errors from waiting handlers
452
- }
457
+ // controller.abort(err) notifies the handler via onResponseError
458
+ waitingHandler.controller.abort(err)
453
459
  }
454
460
 
455
461
  #pruneDoneWaitingHandlers () {