effect 4.0.4 → 4.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/ai-docs/src/52_graphql/10_basics.ts +19 -13
  2. package/ai-docs/src/52_graphql/30_batching.ts +6 -4
  3. package/ai-docs/src/52_graphql/40_aliased-batching.ts +6 -4
  4. package/dist/Array.d.ts +90 -6
  5. package/dist/Array.d.ts.map +1 -1
  6. package/dist/Array.js +4 -2
  7. package/dist/Array.js.map +1 -1
  8. package/dist/Channel.d.ts +6 -1
  9. package/dist/Channel.d.ts.map +1 -1
  10. package/dist/Channel.js +17 -3
  11. package/dist/Channel.js.map +1 -1
  12. package/dist/ConfigProvider.d.ts.map +1 -1
  13. package/dist/ConfigProvider.js +33 -32
  14. package/dist/ConfigProvider.js.map +1 -1
  15. package/dist/Duration.d.ts.map +1 -1
  16. package/dist/Duration.js +2 -5
  17. package/dist/Duration.js.map +1 -1
  18. package/dist/Effect.d.ts +61 -19
  19. package/dist/Effect.d.ts.map +1 -1
  20. package/dist/Effect.js +58 -39
  21. package/dist/Effect.js.map +1 -1
  22. package/dist/FileSystem.d.ts +23 -9
  23. package/dist/FileSystem.d.ts.map +1 -1
  24. package/dist/FileSystem.js.map +1 -1
  25. package/dist/Match.d.ts +20 -6
  26. package/dist/Match.d.ts.map +1 -1
  27. package/dist/Match.js +2 -2
  28. package/dist/Match.js.map +1 -1
  29. package/dist/Path.d.ts +37 -5
  30. package/dist/Path.d.ts.map +1 -1
  31. package/dist/Path.js +36 -5
  32. package/dist/Path.js.map +1 -1
  33. package/dist/Schema.d.ts +7 -2
  34. package/dist/Schema.d.ts.map +1 -1
  35. package/dist/Schema.js +9 -2
  36. package/dist/Schema.js.map +1 -1
  37. package/dist/String.d.ts +12 -0
  38. package/dist/String.d.ts.map +1 -1
  39. package/dist/String.js +17 -4
  40. package/dist/String.js.map +1 -1
  41. package/dist/TxRef.d.ts.map +1 -1
  42. package/dist/TxRef.js +3 -2
  43. package/dist/TxRef.js.map +1 -1
  44. package/dist/ai/AiError.d.ts +10 -10
  45. package/dist/ai/AiError.d.ts.map +1 -1
  46. package/dist/ai/AiError.js +1 -1
  47. package/dist/ai/AiError.js.map +1 -1
  48. package/dist/ai/Decision.d.ts +70 -19
  49. package/dist/ai/Decision.d.ts.map +1 -1
  50. package/dist/ai/Decision.js +37 -3
  51. package/dist/ai/Decision.js.map +1 -1
  52. package/dist/ai/DecisionModel.js +1 -1
  53. package/dist/ai/DecisionModel.js.map +1 -1
  54. package/dist/ai/LanguageModel.d.ts.map +1 -1
  55. package/dist/ai/LanguageModel.js +47 -35
  56. package/dist/ai/LanguageModel.js.map +1 -1
  57. package/dist/ai/McpServer.d.ts +6 -52
  58. package/dist/ai/McpServer.d.ts.map +1 -1
  59. package/dist/ai/McpServer.js +2 -28
  60. package/dist/ai/McpServer.js.map +1 -1
  61. package/dist/ai/Response.d.ts +1 -1
  62. package/dist/cli/Completions.d.ts +5 -0
  63. package/dist/cli/Completions.d.ts.map +1 -1
  64. package/dist/cli/Completions.js.map +1 -1
  65. package/dist/cli/Flag.d.ts +100 -0
  66. package/dist/cli/Flag.d.ts.map +1 -1
  67. package/dist/cli/Flag.js +33 -0
  68. package/dist/cli/Flag.js.map +1 -1
  69. package/dist/cli/Param.d.ts +87 -0
  70. package/dist/cli/Param.d.ts.map +1 -1
  71. package/dist/cli/Param.js +30 -0
  72. package/dist/cli/Param.js.map +1 -1
  73. package/dist/cli/internal/command.d.ts.map +1 -1
  74. package/dist/cli/internal/command.js +2 -1
  75. package/dist/cli/internal/command.js.map +1 -1
  76. package/dist/cli/internal/completions/bash.js +4 -3
  77. package/dist/cli/internal/completions/bash.js.map +1 -1
  78. package/dist/cli/internal/completions/descriptor.js +8 -1
  79. package/dist/cli/internal/completions/descriptor.js.map +1 -1
  80. package/dist/cli/internal/completions/fish.js +4 -4
  81. package/dist/cli/internal/completions/fish.js.map +1 -1
  82. package/dist/cli/internal/completions/powershell.js +3 -2
  83. package/dist/cli/internal/completions/powershell.js.map +1 -1
  84. package/dist/cli/internal/completions/zsh.js +7 -3
  85. package/dist/cli/internal/completions/zsh.js.map +1 -1
  86. package/dist/cli/internal/parser.js +11 -3
  87. package/dist/cli/internal/parser.js.map +1 -1
  88. package/dist/cli/internal/wizard.d.ts.map +1 -1
  89. package/dist/cli/internal/wizard.js +2 -1
  90. package/dist/cli/internal/wizard.js.map +1 -1
  91. package/dist/cluster/ClusterWorkflowEngine.d.ts +1 -0
  92. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  93. package/dist/cluster/ClusterWorkflowEngine.js +67 -13
  94. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  95. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  96. package/dist/cluster/MessageStorage.js +20 -12
  97. package/dist/cluster/MessageStorage.js.map +1 -1
  98. package/dist/cluster/Runners.d.ts.map +1 -1
  99. package/dist/cluster/Runners.js +54 -34
  100. package/dist/cluster/Runners.js.map +1 -1
  101. package/dist/cluster/internal/entityManager.js +1 -1
  102. package/dist/cluster/internal/entityManager.js.map +1 -1
  103. package/dist/encoding/Sse.js +1 -1
  104. package/dist/encoding/Sse.js.map +1 -1
  105. package/dist/graphql/GraphQLClient.d.ts.map +1 -1
  106. package/dist/graphql/GraphQLClient.js +12 -5
  107. package/dist/graphql/GraphQLClient.js.map +1 -1
  108. package/dist/graphql/GraphQLProtocol.d.ts +249 -11
  109. package/dist/graphql/GraphQLProtocol.d.ts.map +1 -1
  110. package/dist/graphql/GraphQLProtocol.js +98 -8
  111. package/dist/graphql/GraphQLProtocol.js.map +1 -1
  112. package/dist/graphql/GraphQLResolver.d.ts +1 -1
  113. package/dist/graphql/GraphQLResolver.d.ts.map +1 -1
  114. package/dist/graphql/GraphQLResolver.js +46 -16
  115. package/dist/graphql/GraphQLResolver.js.map +1 -1
  116. package/dist/http/FetchHttpClient.d.ts +6 -3
  117. package/dist/http/FetchHttpClient.d.ts.map +1 -1
  118. package/dist/http/FetchHttpClient.js +6 -3
  119. package/dist/http/FetchHttpClient.js.map +1 -1
  120. package/dist/http/FindMyWay/internal/router.js +2 -1
  121. package/dist/http/FindMyWay/internal/router.js.map +1 -1
  122. package/dist/http/Headers.d.ts +3 -3
  123. package/dist/http/Headers.d.ts.map +1 -1
  124. package/dist/http/Headers.js +6 -5
  125. package/dist/http/Headers.js.map +1 -1
  126. package/dist/http/HttpClient.d.ts +12 -0
  127. package/dist/http/HttpClient.d.ts.map +1 -1
  128. package/dist/http/HttpClient.js +14 -1
  129. package/dist/http/HttpClient.js.map +1 -1
  130. package/dist/http/HttpClientError.d.ts.map +1 -1
  131. package/dist/http/HttpClientError.js +8 -6
  132. package/dist/http/HttpClientError.js.map +1 -1
  133. package/dist/http/HttpClientRequest.d.ts +147 -4
  134. package/dist/http/HttpClientRequest.d.ts.map +1 -1
  135. package/dist/http/HttpClientRequest.js +47 -1
  136. package/dist/http/HttpClientRequest.js.map +1 -1
  137. package/dist/http/HttpMiddleware.d.ts +60 -0
  138. package/dist/http/HttpMiddleware.d.ts.map +1 -1
  139. package/dist/http/HttpMiddleware.js +67 -5
  140. package/dist/http/HttpMiddleware.js.map +1 -1
  141. package/dist/http/HttpRouter.d.ts.map +1 -1
  142. package/dist/http/HttpRouter.js +52 -13
  143. package/dist/http/HttpRouter.js.map +1 -1
  144. package/dist/http/HttpServerError.d.ts.map +1 -1
  145. package/dist/http/HttpServerError.js +6 -4
  146. package/dist/http/HttpServerError.js.map +1 -1
  147. package/dist/http/HttpServerRequest.js +2 -2
  148. package/dist/http/HttpServerRequest.js.map +1 -1
  149. package/dist/http/HttpStaticServer.d.ts.map +1 -1
  150. package/dist/http/HttpStaticServer.js +4 -10
  151. package/dist/http/HttpStaticServer.js.map +1 -1
  152. package/dist/http/HttpTraceContext.js +1 -1
  153. package/dist/http/HttpTraceContext.js.map +1 -1
  154. package/dist/http/Url.d.ts.map +1 -1
  155. package/dist/http/Url.js +2 -1
  156. package/dist/http/Url.js.map +1 -1
  157. package/dist/http/UrlParams.d.ts +112 -8
  158. package/dist/http/UrlParams.d.ts.map +1 -1
  159. package/dist/http/UrlParams.js +141 -14
  160. package/dist/http/UrlParams.js.map +1 -1
  161. package/dist/http/internal/compression.d.ts +1 -1
  162. package/dist/http/internal/compression.d.ts.map +1 -1
  163. package/dist/http/internal/compression.js +2 -10
  164. package/dist/http/internal/compression.js.map +1 -1
  165. package/dist/http/internal/compressionBody.d.ts +2 -0
  166. package/dist/http/internal/compressionBody.d.ts.map +1 -0
  167. package/dist/http/internal/compressionBody.js +36 -0
  168. package/dist/http/internal/compressionBody.js.map +1 -0
  169. package/dist/http/internal/decompression.d.ts +2 -0
  170. package/dist/http/internal/decompression.d.ts.map +1 -0
  171. package/dist/http/internal/decompression.js +162 -0
  172. package/dist/http/internal/decompression.js.map +1 -0
  173. package/dist/http/internal/tracing.js +14 -4
  174. package/dist/http/internal/tracing.js.map +1 -1
  175. package/dist/http/internal/urlParams.d.ts +2 -0
  176. package/dist/http/internal/urlParams.d.ts.map +1 -0
  177. package/dist/http/internal/urlParams.js +4 -0
  178. package/dist/http/internal/urlParams.js.map +1 -0
  179. package/dist/http-api/HttpApiClient.d.ts.map +1 -1
  180. package/dist/http-api/HttpApiClient.js +2 -1
  181. package/dist/http-api/HttpApiClient.js.map +1 -1
  182. package/dist/internal/core.js +17 -1
  183. package/dist/internal/core.js.map +1 -1
  184. package/dist/internal/effect.js +5 -3
  185. package/dist/internal/effect.js.map +1 -1
  186. package/dist/internal/graphql.js +12 -2
  187. package/dist/internal/graphql.js.map +1 -1
  188. package/dist/internal/version.js +1 -1
  189. package/dist/observability/Otlp.d.ts +15 -0
  190. package/dist/observability/Otlp.d.ts.map +1 -1
  191. package/dist/observability/Otlp.js +3 -0
  192. package/dist/observability/Otlp.js.map +1 -1
  193. package/dist/observability/OtlpExporter.d.ts +8 -0
  194. package/dist/observability/OtlpExporter.d.ts.map +1 -1
  195. package/dist/observability/OtlpExporter.js +10 -1
  196. package/dist/observability/OtlpExporter.js.map +1 -1
  197. package/dist/observability/OtlpLogger.d.ts +17 -0
  198. package/dist/observability/OtlpLogger.d.ts.map +1 -1
  199. package/dist/observability/OtlpLogger.js +14 -1
  200. package/dist/observability/OtlpLogger.js.map +1 -1
  201. package/dist/observability/OtlpMetrics.d.ts +17 -0
  202. package/dist/observability/OtlpMetrics.d.ts.map +1 -1
  203. package/dist/observability/OtlpMetrics.js +13 -0
  204. package/dist/observability/OtlpMetrics.js.map +1 -1
  205. package/dist/observability/OtlpTracer.d.ts +17 -0
  206. package/dist/observability/OtlpTracer.d.ts.map +1 -1
  207. package/dist/observability/OtlpTracer.js +15 -2
  208. package/dist/observability/OtlpTracer.js.map +1 -1
  209. package/dist/reactivity/AsyncResult.d.ts +6 -0
  210. package/dist/reactivity/AsyncResult.d.ts.map +1 -1
  211. package/dist/reactivity/AsyncResult.js +27 -0
  212. package/dist/reactivity/AsyncResult.js.map +1 -1
  213. package/dist/reactivity/AtomRegistry.js +3 -1
  214. package/dist/reactivity/AtomRegistry.js.map +1 -1
  215. package/dist/rpc/RpcClient.d.ts.map +1 -1
  216. package/dist/rpc/RpcClient.js +6 -1
  217. package/dist/rpc/RpcClient.js.map +1 -1
  218. package/dist/sql/SqlClient.d.ts +8 -7
  219. package/dist/sql/SqlClient.d.ts.map +1 -1
  220. package/dist/sql/SqlClient.js +8 -7
  221. package/dist/sql/SqlClient.js.map +1 -1
  222. package/dist/testing/TestClock.d.ts +16 -3
  223. package/dist/testing/TestClock.d.ts.map +1 -1
  224. package/dist/testing/TestClock.js +112 -22
  225. package/dist/testing/TestClock.js.map +1 -1
  226. package/dist/workflow/Activity.js +1 -0
  227. package/dist/workflow/Activity.js.map +1 -1
  228. package/dist/workflow/Workflow.d.ts +22 -0
  229. package/dist/workflow/Workflow.d.ts.map +1 -1
  230. package/dist/workflow/Workflow.js +15 -3
  231. package/dist/workflow/Workflow.js.map +1 -1
  232. package/dist/workflow/WorkflowEngine.d.ts +20 -1
  233. package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
  234. package/dist/workflow/WorkflowEngine.js +35 -21
  235. package/dist/workflow/WorkflowEngine.js.map +1 -1
  236. package/package.json +1 -1
  237. package/src/Array.ts +95 -6
  238. package/src/Channel.ts +24 -3
  239. package/src/ConfigProvider.ts +40 -44
  240. package/src/Duration.ts +4 -5
  241. package/src/Effect.ts +140 -53
  242. package/src/FileSystem.ts +23 -9
  243. package/src/Match.ts +65 -12
  244. package/src/Path.ts +52 -7
  245. package/src/Schema.ts +17 -4
  246. package/src/String.ts +17 -4
  247. package/src/TxRef.ts +4 -2
  248. package/src/ai/AiError.ts +3 -1
  249. package/src/ai/Decision.ts +70 -21
  250. package/src/ai/DecisionModel.ts +1 -1
  251. package/src/ai/LanguageModel.ts +57 -51
  252. package/src/ai/McpServer.ts +15 -55
  253. package/src/cli/Completions.ts +5 -0
  254. package/src/cli/Flag.ts +101 -0
  255. package/src/cli/Param.ts +96 -0
  256. package/src/cli/internal/command.ts +2 -1
  257. package/src/cli/internal/completions/bash.ts +4 -3
  258. package/src/cli/internal/completions/descriptor.ts +10 -1
  259. package/src/cli/internal/completions/fish.ts +4 -4
  260. package/src/cli/internal/completions/powershell.ts +5 -2
  261. package/src/cli/internal/completions/zsh.ts +7 -3
  262. package/src/cli/internal/parser.ts +12 -3
  263. package/src/cli/internal/wizard.ts +2 -1
  264. package/src/cluster/ClusterWorkflowEngine.ts +92 -24
  265. package/src/cluster/MessageStorage.ts +14 -3
  266. package/src/cluster/Runners.ts +88 -66
  267. package/src/cluster/internal/entityManager.ts +1 -1
  268. package/src/encoding/Sse.ts +1 -1
  269. package/src/graphql/GraphQLClient.ts +17 -5
  270. package/src/graphql/GraphQLProtocol.ts +274 -20
  271. package/src/graphql/GraphQLResolver.ts +45 -17
  272. package/src/http/FetchHttpClient.ts +6 -3
  273. package/src/http/FindMyWay/internal/router.ts +2 -1
  274. package/src/http/Headers.ts +6 -9
  275. package/src/http/HttpClient.ts +18 -1
  276. package/src/http/HttpClientError.ts +10 -6
  277. package/src/http/HttpClientRequest.ts +173 -7
  278. package/src/http/HttpMiddleware.ts +85 -5
  279. package/src/http/HttpRouter.ts +60 -13
  280. package/src/http/HttpServerError.ts +8 -4
  281. package/src/http/HttpServerRequest.ts +2 -2
  282. package/src/http/HttpStaticServer.ts +4 -10
  283. package/src/http/HttpTraceContext.ts +1 -1
  284. package/src/http/Url.ts +2 -1
  285. package/src/http/UrlParams.ts +225 -26
  286. package/src/http/internal/compression.ts +3 -17
  287. package/src/http/internal/compressionBody.ts +56 -0
  288. package/src/http/internal/decompression.ts +226 -0
  289. package/src/http/internal/tracing.ts +21 -4
  290. package/src/http/internal/urlParams.ts +5 -0
  291. package/src/http-api/HttpApiClient.ts +2 -1
  292. package/src/internal/core.ts +21 -1
  293. package/src/internal/effect.ts +5 -4
  294. package/src/internal/graphql.ts +30 -7
  295. package/src/internal/version.ts +1 -1
  296. package/src/observability/Otlp.ts +18 -0
  297. package/src/observability/OtlpExporter.ts +14 -3
  298. package/src/observability/OtlpLogger.ts +31 -2
  299. package/src/observability/OtlpMetrics.ts +30 -1
  300. package/src/observability/OtlpTracer.ts +32 -3
  301. package/src/reactivity/AsyncResult.ts +31 -0
  302. package/src/reactivity/AtomRegistry.ts +3 -1
  303. package/src/rpc/RpcClient.ts +6 -1
  304. package/src/sql/SqlClient.ts +9 -7
  305. package/src/testing/TestClock.ts +144 -29
  306. package/src/workflow/Activity.ts +1 -0
  307. package/src/workflow/Workflow.ts +31 -5
  308. package/src/workflow/WorkflowEngine.ts +60 -22
@@ -731,10 +731,11 @@ export const isRedactedName = (
731
731
  name: string,
732
732
  patterns: ReadonlyArray<string | RegExp>
733
733
  ): boolean => {
734
+ const lowerName = name.toLowerCase()
734
735
  for (let i = 0; i < patterns.length; i++) {
735
736
  const pattern = patterns[i]
736
737
  if (typeof pattern === "string") {
737
- if (pattern.toLowerCase() === name.toLowerCase()) {
738
+ if (pattern.toLowerCase() === lowerName) {
738
739
  return true
739
740
  }
740
741
  } else if (name.search(pattern) !== -1) {
@@ -749,9 +750,9 @@ export const isRedactedName = (
749
750
  *
750
751
  * **Details**
751
752
  *
752
- * Defaults include `authorization`, `cookie`, `set-cookie`, `x-api-key` and
753
- * `AWSAccessKeyId`, `Signature`, `sig` and `X-Goog-Signature`. Names match
754
- * case-insensitively, and providing a new list replaces the defaults.
753
+ * Defaults include `authorization`, `cookie`, `set-cookie` and `x-api-key`.
754
+ * A custom list replaces the defaults. Matching uses `isRedactedName`.
755
+ * Use `UrlParams.CurrentRedactedNames` for query parameters.
755
756
  *
756
757
  * @stability unstable
757
758
  * @category services
@@ -764,10 +765,6 @@ export const CurrentRedactedNames = Context.Reference<
764
765
  "authorization",
765
766
  "cookie",
766
767
  "set-cookie",
767
- "x-api-key",
768
- "AWSAccessKeyId",
769
- "Signature",
770
- "sig",
771
- "X-Goog-Signature"
768
+ "x-api-key"
772
769
  ]
773
770
  })
@@ -47,6 +47,7 @@ import * as HttpMethod from "./HttpMethod.ts"
47
47
  import * as TraceContext from "./HttpTraceContext.ts"
48
48
  import * as tracing from "./internal/tracing.ts"
49
49
  import * as Url from "./Url.ts"
50
+ import * as UrlParams from "./UrlParams.ts"
50
51
 
51
52
  const TypeId = "~effect/http/HttpClient"
52
53
 
@@ -905,7 +906,11 @@ export const make = (
905
906
  (span) => {
906
907
  span.attribute("http.request.method", request.method)
907
908
  const redactedHeaderNames = fiber.getRef(Headers.CurrentRedactedNames)
908
- tracing.addUrlAttributes(span, url)
909
+ tracing.addUrlAttributes(
910
+ span,
911
+ url,
912
+ tracing.redactedNames(request.urlParams, fiber.getRef(UrlParams.CurrentRedactedNames))
913
+ )
909
914
  const headerFilter = fiber.getRef(TracerHeaderFilter)
910
915
  tracing.addHeaderAttributes(span, "request", request.headers, headerFilter, redactedHeaderNames)
911
916
  request = fiber.getRef(TracerPropagationEnabled)
@@ -2255,6 +2260,18 @@ export const TracerHeaderFilter = Context.Reference<
2255
2260
  /**
2256
2261
  * Context reference that controls whether outgoing client spans are propagated to request headers.
2257
2262
  *
2263
+ * **Details**
2264
+ *
2265
+ * When this is `true`, the client writes the compact B3 `b3` header and the
2266
+ * W3C `traceparent` header. The default is on. The client span is still
2267
+ * created when this is `false`.
2268
+ *
2269
+ * **Gotchas**
2270
+ *
2271
+ * A browser preflight fails when the target does not list `b3` and
2272
+ * `traceparent` in `Access-Control-Allow-Headers`. Set this to `false` for
2273
+ * those requests.
2274
+ *
2258
2275
  * @stability unstable
2259
2276
  * @category services
2260
2277
  * @since 4.0.0
@@ -15,6 +15,7 @@ import { hasProperty } from "../Predicate.ts"
15
15
  import * as Schema from "../Schema.ts"
16
16
  import type * as HttpClientRequest from "./HttpClientRequest.ts"
17
17
  import type * as ClientResponse from "./HttpClientResponse.ts"
18
+ import * as UrlParams from "./UrlParams.ts"
18
19
 
19
20
  const TypeId = "~effect/http/HttpClientError"
20
21
 
@@ -88,6 +89,9 @@ const formatReason = (tag: string) => tag.endsWith("Error") ? tag.slice(0, -5) :
88
89
  const formatMessage = (reason: string, description: string | undefined, info: string) =>
89
90
  description ? `${reason}: ${description} (${info})` : `${reason} error (${info})`
90
91
 
92
+ const formatMethodAndUrl = (request: HttpClientRequest.HttpClientRequest) =>
93
+ `${request.method} ${UrlParams.redactUrl(request.url)}`
94
+
91
95
  /**
92
96
  * Error describing transport-level failures that occur while sending an HTTP request.
93
97
  *
@@ -107,7 +111,7 @@ export class TransportError extends Data.TaggedError("TransportError")<{
107
111
  * @since 4.0.0
108
112
  */
109
113
  get methodAndUrl() {
110
- return `${this.request.method} ${this.request.url}`
114
+ return formatMethodAndUrl(this.request)
111
115
  }
112
116
 
113
117
  /**
@@ -140,7 +144,7 @@ export class EncodeError extends Data.TaggedError("EncodeError")<{
140
144
  * @since 4.0.0
141
145
  */
142
146
  get methodAndUrl() {
143
- return `${this.request.method} ${this.request.url}`
147
+ return formatMethodAndUrl(this.request)
144
148
  }
145
149
 
146
150
  /**
@@ -173,7 +177,7 @@ export class InvalidUrlError extends Data.TaggedError("InvalidUrlError")<{
173
177
  * @since 4.0.0
174
178
  */
175
179
  get methodAndUrl() {
176
- return `${this.request.method} ${this.request.url}`
180
+ return formatMethodAndUrl(this.request)
177
181
  }
178
182
 
179
183
  /**
@@ -207,7 +211,7 @@ export class StatusCodeError extends Data.TaggedError("StatusCodeError")<{
207
211
  * @since 4.0.0
208
212
  */
209
213
  get methodAndUrl() {
210
- return `${this.request.method} ${this.request.url}`
214
+ return formatMethodAndUrl(this.request)
211
215
  }
212
216
 
213
217
  /**
@@ -242,7 +246,7 @@ export class DecodeError extends Data.TaggedError("DecodeError")<{
242
246
  * @since 4.0.0
243
247
  */
244
248
  get methodAndUrl() {
245
- return `${this.request.method} ${this.request.url}`
249
+ return formatMethodAndUrl(this.request)
246
250
  }
247
251
 
248
252
  /**
@@ -277,7 +281,7 @@ export class EmptyBodyError extends Data.TaggedError("EmptyBodyError")<{
277
281
  * @since 4.0.0
278
282
  */
279
283
  get methodAndUrl() {
280
- return `${this.request.method} ${this.request.url}`
284
+ return formatMethodAndUrl(this.request)
281
285
  }
282
286
 
283
287
  /**
@@ -30,6 +30,7 @@ import * as Stream from "../Stream.ts"
30
30
  import * as Headers from "./Headers.ts"
31
31
  import * as HttpBody from "./HttpBody.ts"
32
32
  import { hasBody, type HttpMethod } from "./HttpMethod.ts"
33
+ import * as compression from "./internal/compressionBody.ts"
33
34
  import * as bodyInternal from "./internal/httpBody.ts"
34
35
  import * as Url from "./Url.ts"
35
36
  import * as UrlParams from "./UrlParams.ts"
@@ -104,7 +105,7 @@ const Proto = {
104
105
  return {
105
106
  _id: "HttpClientRequest",
106
107
  method: this.method,
107
- url: this.url,
108
+ url: UrlParams.redactUrl(this.url),
108
109
  urlParams: this.urlParams,
109
110
  hash: this.hash,
110
111
  headers: redact(this.headers),
@@ -755,6 +756,10 @@ export const updateUrl: {
755
756
  /**
756
757
  * Sets one query parameter, replacing existing values for that parameter name.
757
758
  *
759
+ * **Details**
760
+ *
761
+ * A `Redacted` value is sent unwrapped and stays redacted in inspection, errors and spans.
762
+ *
758
763
  * @stability unstable
759
764
  * @category combinators
760
765
  * @since 4.0.0
@@ -763,20 +768,28 @@ export const setUrlParam: {
763
768
  /**
764
769
  * Sets one query parameter, replacing existing values for that parameter name.
765
770
  *
771
+ * **Details**
772
+ *
773
+ * A `Redacted` value is sent unwrapped and stays redacted in inspection, errors and spans.
774
+ *
766
775
  * @stability unstable
767
776
  * @category combinators
768
777
  * @since 4.0.0
769
778
  */
770
- (key: string, value: string): (self: HttpClientRequest) => HttpClientRequest
779
+ (key: string, value: string | Redacted.Redacted): (self: HttpClientRequest) => HttpClientRequest
771
780
  /**
772
781
  * Sets one query parameter, replacing existing values for that parameter name.
773
782
  *
783
+ * **Details**
784
+ *
785
+ * A `Redacted` value is sent unwrapped and stays redacted in inspection, errors and spans.
786
+ *
774
787
  * @stability unstable
775
788
  * @category combinators
776
789
  * @since 4.0.0
777
790
  */
778
- (self: HttpClientRequest, key: string, value: string): HttpClientRequest
779
- } = dual(3, (self: HttpClientRequest, key: string, value: string): HttpClientRequest =>
791
+ (self: HttpClientRequest, key: string, value: string | Redacted.Redacted): HttpClientRequest
792
+ } = dual(3, (self: HttpClientRequest, key: string, value: string | Redacted.Redacted): HttpClientRequest =>
780
793
  makeWith(
781
794
  self.method,
782
795
  self.url,
@@ -823,6 +836,10 @@ export const setUrlParams: {
823
836
  /**
824
837
  * Appends one query parameter value without removing existing values for the same name.
825
838
  *
839
+ * **Details**
840
+ *
841
+ * A `Redacted` value is sent unwrapped and stays redacted in inspection, errors and spans.
842
+ *
826
843
  * @stability unstable
827
844
  * @category combinators
828
845
  * @since 4.0.0
@@ -831,20 +848,28 @@ export const appendUrlParam: {
831
848
  /**
832
849
  * Appends one query parameter value without removing existing values for the same name.
833
850
  *
851
+ * **Details**
852
+ *
853
+ * A `Redacted` value is sent unwrapped and stays redacted in inspection, errors and spans.
854
+ *
834
855
  * @stability unstable
835
856
  * @category combinators
836
857
  * @since 4.0.0
837
858
  */
838
- (key: string, value: string): (self: HttpClientRequest) => HttpClientRequest
859
+ (key: string, value: string | Redacted.Redacted): (self: HttpClientRequest) => HttpClientRequest
839
860
  /**
840
861
  * Appends one query parameter value without removing existing values for the same name.
841
862
  *
863
+ * **Details**
864
+ *
865
+ * A `Redacted` value is sent unwrapped and stays redacted in inspection, errors and spans.
866
+ *
842
867
  * @stability unstable
843
868
  * @category combinators
844
869
  * @since 4.0.0
845
870
  */
846
- (self: HttpClientRequest, key: string, value: string): HttpClientRequest
847
- } = dual(3, (self: HttpClientRequest, key: string, value: string): HttpClientRequest =>
871
+ (self: HttpClientRequest, key: string, value: string | Redacted.Redacted): HttpClientRequest
872
+ } = dual(3, (self: HttpClientRequest, key: string, value: string | Redacted.Redacted): HttpClientRequest =>
848
873
  makeWith(
849
874
  self.method,
850
875
  self.url,
@@ -974,6 +999,147 @@ export const setBody: {
974
999
  )
975
1000
  })
976
1001
 
1002
+ /**
1003
+ * Algorithms supported by Web `CompressionStream` for request body compression.
1004
+ *
1005
+ * @stability unstable
1006
+ * @category models
1007
+ * @since 4.0.0
1008
+ */
1009
+ export type CompressionAlgorithm = "gzip" | "deflate"
1010
+
1011
+ /**
1012
+ * Options for request body compression.
1013
+ *
1014
+ * @stability unstable
1015
+ * @category options
1016
+ * @since 4.0.0
1017
+ */
1018
+ export interface CompressionOptions {
1019
+ /**
1020
+ * Minimum known body length in bytes to compress. Defaults to `1024`.
1021
+ * Bodies with unknown length are always compressed.
1022
+ */
1023
+ readonly minSize?: number | undefined
1024
+ }
1025
+
1026
+ /**
1027
+ * Compresses a request body using Web `CompressionStream` and sets `Content-Encoding`.
1028
+ *
1029
+ * **Details**
1030
+ *
1031
+ * Byte-array bodies (including text, JSON, and URL-encoded bodies) are compressed
1032
+ * into a new `Uint8Array` body, preserving the content type and updating
1033
+ * `Content-Length`. The compressed body does not retain the original lazy text.
1034
+ * Stream bodies are compressed lazily and have their content length removed.
1035
+ * They still require a client runtime that supports streaming request bodies
1036
+ * (Fetch requires `duplex: "half"`, which Safari and Firefox do not support).
1037
+ *
1038
+ * Returns the original request unchanged for empty, FormData, and raw bodies,
1039
+ * bodies with a known length below `options.minSize`, or requests with any
1040
+ * existing `Content-Encoding` header, including `identity`. Unknown-length
1041
+ * streams are always compressed. The default minimum is 1024 bytes because
1042
+ * gzip can make very small bodies larger.
1043
+ *
1044
+ * Requires `CompressionStream` (available in browsers, Node 18+, Bun, and Deno).
1045
+ * There is no typed error channel: byte compression failures are defects;
1046
+ * stream failures surface when the body is sent.
1047
+ *
1048
+ * To compress every request, use
1049
+ * `HttpClient.mapRequestEffect(HttpClientRequest.compressBody("gzip"))`.
1050
+ * `HttpClient.retryTransient` re-runs preprocessing, so this recompresses on
1051
+ * every retry. For large bodies, compress once before calling `client.execute`.
1052
+ *
1053
+ * @stability unstable
1054
+ * @category combinators
1055
+ * @since 4.0.0
1056
+ */
1057
+ export const compressBody: {
1058
+ /**
1059
+ * Compresses a request body using Web `CompressionStream` and sets `Content-Encoding`.
1060
+ *
1061
+ * **Details**
1062
+ *
1063
+ * Byte-array bodies (including text, JSON, and URL-encoded bodies) are compressed
1064
+ * into a new `Uint8Array` body, preserving the content type and updating
1065
+ * `Content-Length`. The compressed body does not retain the original lazy text.
1066
+ * Stream bodies are compressed lazily and have their content length removed.
1067
+ * They still require a client runtime that supports streaming request bodies
1068
+ * (Fetch requires `duplex: "half"`, which Safari and Firefox do not support).
1069
+ *
1070
+ * Returns the original request unchanged for empty, FormData, and raw bodies,
1071
+ * bodies with a known length below `options.minSize`, or requests with any
1072
+ * existing `Content-Encoding` header, including `identity`. Unknown-length
1073
+ * streams are always compressed. The default minimum is 1024 bytes because
1074
+ * gzip can make very small bodies larger.
1075
+ *
1076
+ * Requires `CompressionStream` (available in browsers, Node 18+, Bun, and Deno).
1077
+ * There is no typed error channel: byte compression failures are defects;
1078
+ * stream failures surface when the body is sent.
1079
+ *
1080
+ * To compress every request, use
1081
+ * `HttpClient.mapRequestEffect(HttpClientRequest.compressBody("gzip"))`.
1082
+ * `HttpClient.retryTransient` re-runs preprocessing, so this recompresses on
1083
+ * every retry. For large bodies, compress once before calling `client.execute`.
1084
+ *
1085
+ * @stability unstable
1086
+ * @category combinators
1087
+ * @since 4.0.0
1088
+ */
1089
+ (algorithm: CompressionAlgorithm, options?: CompressionOptions | undefined): (self: HttpClientRequest) => Effect.Effect<HttpClientRequest>
1090
+ /**
1091
+ * Compresses a request body using Web `CompressionStream` and sets `Content-Encoding`.
1092
+ *
1093
+ * **Details**
1094
+ *
1095
+ * Byte-array bodies (including text, JSON, and URL-encoded bodies) are compressed
1096
+ * into a new `Uint8Array` body, preserving the content type and updating
1097
+ * `Content-Length`. The compressed body does not retain the original lazy text.
1098
+ * Stream bodies are compressed lazily and have their content length removed.
1099
+ * They still require a client runtime that supports streaming request bodies
1100
+ * (Fetch requires `duplex: "half"`, which Safari and Firefox do not support).
1101
+ *
1102
+ * Returns the original request unchanged for empty, FormData, and raw bodies,
1103
+ * bodies with a known length below `options.minSize`, or requests with any
1104
+ * existing `Content-Encoding` header, including `identity`. Unknown-length
1105
+ * streams are always compressed. The default minimum is 1024 bytes because
1106
+ * gzip can make very small bodies larger.
1107
+ *
1108
+ * Requires `CompressionStream` (available in browsers, Node 18+, Bun, and Deno).
1109
+ * There is no typed error channel: byte compression failures are defects;
1110
+ * stream failures surface when the body is sent.
1111
+ *
1112
+ * To compress every request, use
1113
+ * `HttpClient.mapRequestEffect(HttpClientRequest.compressBody("gzip"))`.
1114
+ * `HttpClient.retryTransient` re-runs preprocessing, so this recompresses on
1115
+ * every retry. For large bodies, compress once before calling `client.execute`.
1116
+ *
1117
+ * @stability unstable
1118
+ * @category combinators
1119
+ * @since 4.0.0
1120
+ */
1121
+ (
1122
+ self: HttpClientRequest,
1123
+ algorithm: CompressionAlgorithm,
1124
+ options?: CompressionOptions | undefined
1125
+ ): Effect.Effect<HttpClientRequest>
1126
+ } = dual(
1127
+ (args) => isHttpClientRequest(args[0]),
1128
+ (
1129
+ self: HttpClientRequest,
1130
+ algorithm: CompressionAlgorithm,
1131
+ options?: CompressionOptions | undefined
1132
+ ): Effect.Effect<HttpClientRequest> => {
1133
+ if (self.headers["content-encoding"] !== undefined) {
1134
+ return Effect.succeed(self)
1135
+ }
1136
+ return Effect.map(
1137
+ compression.compressBody(self.body, algorithm, options),
1138
+ (body) => body === self.body ? self : setHeader(setBody(self, body), "content-encoding", algorithm)
1139
+ )
1140
+ }
1141
+ )
1142
+
977
1143
  /**
978
1144
  * Sets a `Uint8Array` request body with an optional content type.
979
1145
  *
@@ -11,6 +11,7 @@
11
11
  * @stability unstable
12
12
  * @since 4.0.0
13
13
  */
14
+ import * as ByteSize from "../ByteSize.ts"
14
15
  import * as Cause from "../Cause.ts"
15
16
  import { Clock } from "../Clock.ts"
16
17
  import * as Context from "../Context.ts"
@@ -26,6 +27,7 @@ import type { Predicate } from "../Predicate.ts"
26
27
  import type { ReadonlyRecord } from "../Record.ts"
27
28
  import { nativeTracer, ParentSpan, Tracer } from "../Tracer.ts"
28
29
  import * as Headers from "./Headers.ts"
30
+ import { hasBody } from "./HttpMethod.ts"
29
31
  import type { CompressionAlgorithm } from "./HttpPlatform.ts"
30
32
  import { HttpPlatform } from "./HttpPlatform.ts"
31
33
  import { causeResponseStripped, ClientAbort, HttpServerError, ResponseError } from "./HttpServerError.ts"
@@ -35,9 +37,11 @@ import * as Response from "./HttpServerResponse.ts"
35
37
  import type { HttpServerResponse } from "./HttpServerResponse.ts"
36
38
  import * as TraceContext from "./HttpTraceContext.ts"
37
39
  import * as compressionInternal from "./internal/compression.ts"
40
+ import * as decompressionInternal from "./internal/decompression.ts"
38
41
  import * as bodyInternal from "./internal/httpBody.ts"
39
42
  import { appendPreResponseHandlerUnsafe } from "./internal/preResponseHandler.ts"
40
43
  import * as tracing from "./internal/tracing.ts"
44
+ import * as UrlParams from "./UrlParams.ts"
41
45
 
42
46
  /**
43
47
  * Middleware that transforms an HTTP server app effect into another HTTP server app effect.
@@ -286,11 +290,7 @@ export const tracer: <E, R>(
286
290
  span.attribute("http.request.method", request.method)
287
291
  const url = Request.toURL(request)
288
292
  if (Option.isSome(url)) {
289
- if (!request.url.startsWith("/") && (url.value.username !== "" || url.value.password !== "")) {
290
- url.value.username = "REDACTED"
291
- url.value.password = "REDACTED"
292
- }
293
- tracing.addUrlAttributes(span, url.value)
293
+ tracing.addUrlAttributes(span, url.value, fiber.getRef(UrlParams.CurrentRedactedNames))
294
294
  }
295
295
  if (request.headers["user-agent"] !== undefined) {
296
296
  span.attribute("user_agent.original", request.headers["user-agent"])
@@ -626,6 +626,86 @@ export const compression = (
626
626
  })
627
627
  }
628
628
 
629
+ /**
630
+ * Middleware that decompresses request bodies sent with a `Content-Encoding`
631
+ * of `gzip` or `deflate`.
632
+ *
633
+ * **Details**
634
+ *
635
+ * The request service is replaced with a view whose body accessors (`stream`,
636
+ * `text`, `json`, `arrayBuffer`, `urlParamsBody`, `multipart`) yield the
637
+ * decompressed body, and whose headers no longer include `Content-Encoding`
638
+ * or `Content-Length`. Decompression is lazy and only happens when the body
639
+ * is read. Stacked codings such as `deflate, gzip` are undone in reverse
640
+ * order, and `identity` members are ignored.
641
+ *
642
+ * Requests whose `Content-Encoding` includes any other coding are rejected
643
+ * with `415 Unsupported Media Type` and `Accept-Encoding: gzip, deflate`,
644
+ * without running the app. Malformed compressed data fails the body read
645
+ * with a `RequestParseError`, or a `MultipartError` with reason `Parse` when
646
+ * reading multipart data.
647
+ *
648
+ * The decompressed size is limited to `maxDecompressedSize`, falling back to
649
+ * the `MaxBodySize` reference and then to 10 MiB. Exceeding it fails the body
650
+ * read with a `RequestParseError`, or a `MultipartError` with reason
651
+ * `BodyTooLarge` when reading multipart data. The limit applies to every body
652
+ * accessor, including `stream`.
653
+ *
654
+ * Decompression uses the global `DecompressionStream`. `deflate` means the
655
+ * zlib format (RFC 1950); raw deflate data is rejected.
656
+ *
657
+ * **Gotchas**
658
+ *
659
+ * `request.source` is still the original, compressed request.
660
+ * `HttpServerRequest.toWeb` returns that compressed request only when the
661
+ * source is a Web `Request`. On other platforms, such as Node, it builds a new
662
+ * `Request` from the decompressed body and the stripped headers.
663
+ *
664
+ * @stability unstable
665
+ * @category compression
666
+ * @since 4.0.0
667
+ */
668
+ export const decompression = (
669
+ options?: {
670
+ /**
671
+ * Maximum number of decompressed body bytes. Defaults to the
672
+ * `MaxBodySize` reference, or 10 MiB when that is unset.
673
+ */
674
+ readonly maxDecompressedSize?: ByteSize.Input | undefined
675
+ } | undefined
676
+ ): <E, R>(
677
+ httpApp: Effect.Effect<HttpServerResponse, E, R>
678
+ ) => Effect.Effect<HttpServerResponse, E, R | HttpServerRequest> => {
679
+ const maxDecompressedSize = options?.maxDecompressedSize === undefined
680
+ ? undefined
681
+ : ByteSize.fromInputUnsafe(options.maxDecompressedSize)
682
+ return <E, R>(
683
+ httpApp: Effect.Effect<HttpServerResponse, E, R>
684
+ ): Effect.Effect<HttpServerResponse, E, R | HttpServerRequest> =>
685
+ Effect.withFiber((fiber) => {
686
+ const request = Context.getUnsafe(fiber.context, HttpServerRequest)
687
+ const contentEncoding = request.headers["content-encoding"]
688
+ if (contentEncoding === undefined || !hasBody(request.method)) {
689
+ return httpApp
690
+ }
691
+ const codings = decompressionInternal.parseContentEncoding(contentEncoding)
692
+ if (codings === undefined) {
693
+ return Effect.succeed(Response.empty({
694
+ status: 415,
695
+ headers: { "accept-encoding": "gzip, deflate" }
696
+ }))
697
+ }
698
+ if (codings.length === 0) {
699
+ return httpApp
700
+ }
701
+ return Effect.provideService(
702
+ httpApp,
703
+ HttpServerRequest,
704
+ new decompressionInternal.DecompressedRequest(request, codings, maxDecompressedSize)
705
+ )
706
+ })
707
+ }
708
+
629
709
  const withVary = (response: HttpServerResponse): HttpServerResponse => {
630
710
  const vary = compressionInternal.varyWith(response.headers, "Accept-Encoding")
631
711
  return Response.setHeader(response, "vary", vary)
@@ -24,6 +24,7 @@ import * as Scope from "../Scope.ts"
24
24
  import * as Tracer from "../Tracer.ts"
25
25
  import type * as Types from "../Types.ts"
26
26
  import * as FindMyWay from "./FindMyWay.ts"
27
+ import { getClosingParenthensePosition } from "./FindMyWay/internal/router.ts"
27
28
  import * as HttpEffect from "./HttpEffect.ts"
28
29
  import * as HttpIncomingMessage from "./HttpIncomingMessage.ts"
29
30
  import type * as HttpMethod from "./HttpMethod.ts"
@@ -123,7 +124,12 @@ export const HttpRouter: Context.Service<HttpRouter, HttpRouter> = Context.Servi
123
124
  * @since 4.0.0
124
125
  */
125
126
  export const make = Effect.gen(function*() {
126
- const router = FindMyWay.make<Route<any, never>>(yield* RouterConfig)
127
+ const config = yield* RouterConfig
128
+ const ignoreDuplicateSlashes = config.ignoreDuplicateSlashes ?? true
129
+ const router = FindMyWay.make<{
130
+ readonly route: Route<any, never>
131
+ readonly prefixSegments: number | undefined
132
+ }>(config)
127
133
  const middleware = new Set<middleware.Fn>()
128
134
 
129
135
  const addAll = <const Routes extends ReadonlyArray<Route<any, any>>>(
@@ -147,19 +153,25 @@ export const make = Effect.gen(function*() {
147
153
  ...routes[i],
148
154
  handler: applyMiddleware(routes[i].handler as Effect.Effect<HttpServerResponse.HttpServerResponse>)
149
155
  })
156
+ const handler = {
157
+ route: route as Route<any, never>,
158
+ prefixSegments: Option.isSome(route.prefix)
159
+ ? countSegments(route.prefix.value, ignoreDuplicateSlashes)
160
+ : undefined
161
+ }
150
162
  if (route.method === "*") {
151
163
  if (route.path.endsWith("/*")) {
152
- router.all(route.path, route as any)
153
- router.all(route.path.slice(0, -2) as any, route as any)
164
+ router.all(route.path, handler)
165
+ router.all(route.path.slice(0, -2) as any, handler)
154
166
  } else {
155
- router.all(route.path, route as any)
167
+ router.all(route.path, handler)
156
168
  }
157
169
  } else {
158
170
  if (route.path.endsWith("/*")) {
159
- router.on(route.method, route.path, route as any)
160
- router.on(route.method, route.path.slice(0, -2) as any, route as any)
171
+ router.on(route.method, route.path, handler)
172
+ router.on(route.method, route.path.slice(0, -2) as any, handler)
161
173
  } else {
162
- router.on(route.method, route.path, route as any)
174
+ router.on(route.method, route.path, handler)
163
175
  }
164
176
  }
165
177
  }
@@ -211,12 +223,12 @@ export const make = Effect.gen(function*() {
211
223
  })
212
224
  )
213
225
  }
214
- const route = result.handler
215
- if (Option.isSome(route.prefix)) {
226
+ const { route, prefixSegments } = result.handler
227
+ if (prefixSegments !== undefined) {
216
228
  context = Context.add(
217
229
  context,
218
230
  HttpServerRequest.HttpServerRequest,
219
- sliceRequestUrl(request, route.prefix.value)
231
+ sliceRequestUrl(request, prefixSegments, ignoreDuplicateSlashes)
220
232
  )
221
233
  }
222
234
  context = Context.add(context, HttpServerRequest.ParsedSearchParams, result.searchParams)
@@ -251,9 +263,44 @@ export const make = Effect.gen(function*() {
251
263
  })
252
264
  })
253
265
 
254
- function sliceRequestUrl(request: HttpServerRequest.HttpServerRequest, prefix: string) {
255
- const prefexLen = prefix.length
256
- return request.modify({ url: request.url.length <= prefexLen ? "/" : request.url.slice(prefexLen) })
266
+ function sliceRequestUrl(
267
+ request: HttpServerRequest.HttpServerRequest,
268
+ prefixSegments: number,
269
+ ignoreDuplicateSlashes: boolean
270
+ ) {
271
+ const url = request.url
272
+ let index = 0
273
+ for (let segments = prefixSegments; segments > 0; segments--) {
274
+ if (ignoreDuplicateSlashes) {
275
+ while (url[index] === "/") index++
276
+ } else if (url[index] === "/") {
277
+ index++
278
+ }
279
+ while (index < url.length && !"/?;#".includes(url[index])) index++
280
+ }
281
+ return request.modify({ url: index < url.length ? url.slice(index) : "/" })
282
+ }
283
+
284
+ function countSegments(prefix: string, ignoreDuplicateSlashes: boolean) {
285
+ let count = 0
286
+ let inParamName = false
287
+ for (let i = 0; i < prefix.length; i++) {
288
+ const char = prefix[i]
289
+ if (char === "/") {
290
+ if (!ignoreDuplicateSlashes || prefix[i - 1] !== "/") count++
291
+ inParamName = false
292
+ } else if (char === ":") {
293
+ if (prefix[i + 1] === ":") i++
294
+ else inParamName = true
295
+ } else if (char === "-" || char === ".") {
296
+ inParamName = false
297
+ } else if (char === "(" && inParamName) {
298
+ // Slashes inside parameter regexes are not segment boundaries.
299
+ i = getClosingParenthensePosition(prefix, i)
300
+ inParamName = false
301
+ }
302
+ }
303
+ return count
257
304
  }
258
305
 
259
306
  /**