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
@@ -23,6 +23,7 @@ import { hasProperty } from "../Predicate.ts"
23
23
  import type * as Request from "./HttpServerRequest.ts"
24
24
  import * as Respondable from "./HttpServerRespondable.ts"
25
25
  import * as Response from "./HttpServerResponse.ts"
26
+ import * as UrlParams from "./UrlParams.ts"
26
27
 
27
28
  const TypeId = "~effect/http/HttpServerError"
28
29
 
@@ -107,7 +108,7 @@ export class RequestParseError extends Data.TaggedError("RequestParseError")<{
107
108
  }
108
109
 
109
110
  get methodAndUrl() {
110
- return `${this.request.method} ${this.request.url}`
111
+ return formatMethodAndUrl(this.request)
111
112
  }
112
113
 
113
114
  override get message() {
@@ -139,7 +140,7 @@ export class RouteNotFound extends Data.TaggedError("RouteNotFound")<{
139
140
  override readonly [ErrorReporter.ignore] = true
140
141
 
141
142
  get methodAndUrl() {
142
- return `${this.request.method} ${this.request.url}`
143
+ return formatMethodAndUrl(this.request)
143
144
  }
144
145
 
145
146
  override get message() {
@@ -174,7 +175,7 @@ export class InternalError extends Data.TaggedError("InternalError")<{
174
175
  }
175
176
 
176
177
  get methodAndUrl() {
177
- return `${this.request.method} ${this.request.url}`
178
+ return formatMethodAndUrl(this.request)
178
179
  }
179
180
 
180
181
  override get message() {
@@ -214,7 +215,7 @@ export class ResponseError extends Data.TaggedError("ResponseError")<{
214
215
  }
215
216
 
216
217
  get methodAndUrl() {
217
- return `${this.request.method} ${this.request.url}`
218
+ return formatMethodAndUrl(this.request)
218
219
  }
219
220
 
220
221
  override get message() {
@@ -275,6 +276,9 @@ export class ClientAbort extends Context.Service<ClientAbort, true>()("effect/ht
275
276
  )
276
277
  }
277
278
 
279
+ const formatMethodAndUrl = (request: Request.HttpServerRequest) =>
280
+ `${request.method} ${UrlParams.redactUrl(request.url)}`
281
+
278
282
  const formatRequestMessage = (reason: string, description: string | undefined, info: string) => {
279
283
  const prefix = `${reason} (${info})`
280
284
  return description ? `${prefix}: ${description}` : prefix
@@ -512,7 +512,7 @@ class ServerRequestImpl extends Inspectable.Class implements HttpServerRequest {
512
512
  return HttpIncomingMessage.inspect(this, {
513
513
  _id: "HttpServerRequest",
514
514
  method: this.method,
515
- url: this.originalUrl
515
+ url: UrlParams.redactUrl(this.originalUrl)
516
516
  })
517
517
  }
518
518
  modify(
@@ -711,7 +711,7 @@ class ClientRequestImpl extends Inspectable.Class implements HttpServerRequest {
711
711
  return HttpIncomingMessage.inspect(this, {
712
712
  _id: "HttpServerRequest",
713
713
  method: this.method,
714
- url: this.originalUrl
714
+ url: UrlParams.redactUrl(this.originalUrl)
715
715
  })
716
716
  }
717
717
 
@@ -13,6 +13,7 @@
13
13
  import * as Effect from "../Effect.ts"
14
14
  import * as FileSystem from "../FileSystem.ts"
15
15
  import * as Layer from "../Layer.ts"
16
+ import * as Option from "../Option.ts"
16
17
  import * as Path from "../Path.ts"
17
18
  import type { PlatformError } from "../PlatformError.ts"
18
19
  import * as HttpPlatform from "./HttpPlatform.ts"
@@ -398,16 +399,9 @@ const resolveFilePath = (path: Path.Path, root: string, url: string): string | u
398
399
  if (decodedPath.includes("\u0000")) {
399
400
  return undefined
400
401
  }
401
- const normalizedPath = path.normalize(decodedPath.startsWith("/") ? decodedPath.slice(1) : decodedPath)
402
- if (normalizedPath === ".." || normalizedPath.startsWith(`..${path.sep}`)) {
403
- return undefined
404
- }
405
- const resolvedPath = path.join(root, normalizedPath)
406
- const rootPrefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`
407
- if (resolvedPath !== root && !resolvedPath.startsWith(rootPrefix)) {
408
- return undefined
409
- }
410
- return resolvedPath
402
+ // every leading separator is part of the URL, not a filesystem root
403
+ const relativePath = decodedPath.replace(path.sep === "\\" ? /^[/\\]+/ : /^\/+/, "")
404
+ return Option.getOrUndefined(path.resolveWithin(root, relativePath))
411
405
  }
412
406
 
413
407
  const toRouteNotFoundError = (request: HttpServerRequest.HttpServerRequest) =>
@@ -49,7 +49,7 @@ export const toHeaders = (span: Tracer.Span): Headers.Headers =>
49
49
  onSome: (parent) => `-${parent.spanId}`
50
50
  })
51
51
  }`,
52
- traceparent: `00-${span.traceId}-${span.spanId}-${span.sampled ? "01" : "00"}`
52
+ traceparent: `00-${span.traceId.padStart(32, "0")}-${span.spanId}-${span.sampled ? "01" : "00"}`
53
53
  })
54
54
 
55
55
  /**
package/src/http/Url.ts CHANGED
@@ -14,6 +14,7 @@ import * as Data from "../Data.ts"
14
14
  import { dual } from "../Function.ts"
15
15
  import * as Redacted from "../Redacted.ts"
16
16
  import * as Result from "../Result.ts"
17
+ import * as internal from "./internal/urlParams.ts"
17
18
  import * as UrlParams from "./UrlParams.ts"
18
19
 
19
20
  /**
@@ -49,7 +50,7 @@ export const make = (
49
50
  for (let i = 0; i < params.params.length; i++) {
50
51
  const [key, value] = params.params[i]
51
52
  if (value !== undefined) {
52
- urlInstance.searchParams.append(key, value)
53
+ urlInstance.searchParams.append(key, internal.unwrap(value))
53
54
  }
54
55
  }
55
56
  if (hash !== undefined) {
@@ -10,6 +10,7 @@
10
10
  * @since 4.0.0
11
11
  */
12
12
  import * as Arr from "../Array.ts"
13
+ import * as Context from "../Context.ts"
13
14
  import * as Equal from "../Equal.ts"
14
15
  import * as Equ from "../Equivalence.ts"
15
16
  import { dual } from "../Function.ts"
@@ -21,7 +22,11 @@ import * as Option from "../Option.ts"
21
22
  import type { Pipeable } from "../Pipeable.ts"
22
23
  import { hasProperty } from "../Predicate.ts"
23
24
  import type { ReadonlyRecord } from "../Record.ts"
25
+ import * as Redactable from "../Redactable.ts"
26
+ import * as Redacted from "../Redacted.ts"
24
27
  import * as Tuple from "../Tuple.ts"
28
+ import * as Headers from "./Headers.ts"
29
+ import * as internal from "./internal/urlParams.ts"
25
30
 
26
31
  const TypeId = "~effect/http/UrlParams"
27
32
 
@@ -30,16 +35,19 @@ const TypeId = "~effect/http/UrlParams"
30
35
  *
31
36
  * **Details**
32
37
  *
33
- * Parameters are stored as ordered string key-value pairs and can contain multiple
34
- * values for the same key.
38
+ * Parameters are ordered key-value pairs, and a key can repeat. Each value is a
39
+ * string or a `Redacted` string, which is sent unwrapped. Inspection redacts
40
+ * `Redacted` values and values whose names match `CurrentRedactedNames`.
35
41
  *
36
42
  * @stability unstable
37
43
  * @category models
38
44
  * @since 4.0.0
39
45
  */
40
- export interface UrlParams extends Pipeable, Inspectable, Iterable<readonly [string, string]> {
46
+ export interface UrlParams
47
+ extends Pipeable, Inspectable, Redactable.Redactable, Iterable<readonly [string, string | Redacted.Redacted]>
48
+ {
41
49
  readonly [TypeId]: typeof TypeId
42
- readonly params: ReadonlyArray<readonly [string, string]>
50
+ readonly params: ReadonlyArray<readonly [string, string | Redacted.Redacted]>
43
51
  }
44
52
 
45
53
  /**
@@ -74,7 +82,11 @@ type CoercibleRecordInput = CoercibleRecord & {
74
82
  }
75
83
 
76
84
  /**
77
- * Primitive value that can be converted into a URL parameter string.
85
+ * Value that can be converted into a URL parameter string.
86
+ *
87
+ * **Details**
88
+ *
89
+ * `Redacted` values are stored wrapped and sent unwrapped.
78
90
  *
79
91
  * **Gotchas**
80
92
  *
@@ -84,7 +96,7 @@ type CoercibleRecordInput = CoercibleRecord & {
84
96
  * @category models
85
97
  * @since 4.0.0
86
98
  */
87
- export type Coercible = string | number | bigint | boolean | null | undefined
99
+ export type Coercible = string | number | bigint | boolean | null | undefined | Redacted.Redacted
88
100
 
89
101
  /**
90
102
  * @stability unstable
@@ -118,12 +130,15 @@ const Proto = {
118
130
  [Symbol.iterator](this: UrlParams) {
119
131
  return this.params[Symbol.iterator]()
120
132
  },
121
- toJSON(this: UrlParams): unknown {
133
+ [Redactable.symbolRedactable](this: UrlParams, context: Context.Context<never>): unknown {
122
134
  return {
123
135
  _id: "UrlParams",
124
- params: Object.fromEntries(this.params)
136
+ params: Object.fromEntries(redact(this, Context.get(context, CurrentRedactedNames)))
125
137
  }
126
138
  },
139
+ toJSON(this: UrlParams): unknown {
140
+ return Redactable.redact(this)
141
+ },
127
142
  [Equal.symbol](this: UrlParams, that: UrlParams): boolean {
128
143
  return Equivalence(this, that)
129
144
  },
@@ -133,7 +148,7 @@ const Proto = {
133
148
  }
134
149
 
135
150
  /**
136
- * Creates `UrlParams` from ordered string key-value pairs.
151
+ * Creates `UrlParams` from ordered key-value pairs.
137
152
  *
138
153
  * **Details**
139
154
  *
@@ -143,7 +158,7 @@ const Proto = {
143
158
  * @category constructors
144
159
  * @since 4.0.0
145
160
  */
146
- export const make = (params: ReadonlyArray<readonly [string, string]>): UrlParams => {
161
+ export const make = (params: ReadonlyArray<readonly [string, string | Redacted.Redacted]>): UrlParams => {
147
162
  const self = Object.create(Proto)
148
163
  self.params = params
149
164
  return self
@@ -156,6 +171,7 @@ export const make = (params: ReadonlyArray<readonly [string, string]>): UrlParam
156
171
  *
157
172
  * Primitive values are converted to strings, arrays produce repeated parameters,
158
173
  * nested records use bracket notation, and `undefined` values are omitted.
174
+ * `Redacted` values are kept as-is.
159
175
  *
160
176
  * @stability unstable
161
177
  * @category constructors
@@ -166,42 +182,44 @@ export const fromInput = (input: Input): UrlParams => {
166
182
  return input
167
183
  }
168
184
  const parsed = fromInputNested(input)
169
- const out: Array<[string, string]> = []
185
+ const out: Array<[string, string | Redacted.Redacted]> = []
170
186
  for (let i = 0; i < parsed.length; i++) {
171
187
  if (Array.isArray(parsed[i][0])) {
172
- const [keys, value] = parsed[i] as [Array<string>, string]
188
+ const [keys, value] = parsed[i] as [Array<string>, string | Redacted.Redacted]
173
189
  out.push([`${keys[0]}[${keys.slice(1).join("][")}]`, value])
174
190
  } else {
175
- out.push(parsed[i] as [string, string])
191
+ out.push(parsed[i] as [string, string | Redacted.Redacted])
176
192
  }
177
193
  }
178
194
  return make(out)
179
195
  }
180
196
 
181
- const fromInputNested = (input: Input): Array<[string | Array<string>, any]> => {
197
+ const fromInputNested = (input: Input): Array<[string | Array<string>, string | Redacted.Redacted]> => {
182
198
  const entries = typeof (input as any)[Symbol.iterator] === "function"
183
199
  ? Arr.fromIterable(input as Iterable<readonly [string, Coercible]>)
184
200
  : Object.entries(input)
185
- const out: Array<[string | Array<string>, string]> = []
201
+ const out: Array<[string | Array<string>, string | Redacted.Redacted]> = []
186
202
  for (const [key, value] of entries) {
187
203
  if (Array.isArray(value)) {
188
204
  for (let i = 0; i < value.length; i++) {
189
205
  if (value[i] !== undefined) {
190
- out.push([key, String(value[i])])
206
+ out.push([key, coerce(value[i])])
191
207
  }
192
208
  }
193
- } else if (value !== null && typeof value === "object") {
209
+ } else if (value !== null && typeof value === "object" && !Redacted.isRedacted(value)) {
194
210
  const nested = fromInputNested(value as CoercibleRecord)
195
211
  for (const [k, v] of nested) {
196
212
  out.push([[key, ...(typeof k === "string" ? [k] : k)], v])
197
213
  }
198
214
  } else if (value !== undefined) {
199
- out.push([key, String(value)])
215
+ out.push([key, coerce(value)])
200
216
  }
201
217
  }
202
218
  return out
203
219
  }
204
220
 
221
+ const coerce = (value: Coercible): string | Redacted.Redacted => Redacted.isRedacted(value) ? value : String(value)
222
+
205
223
  /**
206
224
  * Provides an order-sensitive `Equivalence` instance for `UrlParams`.
207
225
  *
@@ -219,7 +237,7 @@ export const Equivalence: Equ.Equivalence<UrlParams> = Equ.make<UrlParams>((a, b
219
237
  )
220
238
 
221
239
  const arrayEquivalence = Arr.makeEquivalence(
222
- Tuple.makeEquivalence([Equ.strictEqual<string>(), Equ.strictEqual<string>()])
240
+ Tuple.makeEquivalence([Equ.strictEqual<string>(), Equal.asEquivalence<string | Redacted.Redacted>()])
223
241
  )
224
242
 
225
243
  /**
@@ -272,7 +290,7 @@ export const getAll: {
272
290
  (self: UrlParams, key: string): ReadonlyArray<string> =>
273
291
  Arr.reduce(self.params, [] as Array<string>, (acc, [k, value]) => {
274
292
  if (k === key) {
275
- acc.push(value)
293
+ acc.push(internal.unwrap(value))
276
294
  }
277
295
  return acc
278
296
  })
@@ -333,7 +351,7 @@ export const getFirst: {
333
351
  2,
334
352
  (self: UrlParams, key: string): Option.Option<string> =>
335
353
  Arr.findFirst(self.params, ([k]) => k === key).pipe(
336
- Option.map(([, value]) => value)
354
+ Option.map(([, value]) => internal.unwrap(value))
337
355
  )
338
356
  )
339
357
 
@@ -390,7 +408,7 @@ export const getLast: {
390
408
  (self: UrlParams, key: string): Option.Option<string>
391
409
  } = dual(2, (self: UrlParams, key: string): Option.Option<string> =>
392
410
  Arr.findLast(self.params, ([k]) => k === key).pipe(
393
- Option.map(([, value]) => value)
411
+ Option.map(([, value]) => internal.unwrap(value))
394
412
  ))
395
413
 
396
414
  /**
@@ -436,7 +454,7 @@ export const set: {
436
454
  make(
437
455
  Arr.append(
438
456
  Arr.filter(self.params, ([k]) => k !== key),
439
- [key, String(value)]
457
+ [key, coerce(value)]
440
458
  )
441
459
  ))
442
460
 
@@ -560,7 +578,7 @@ export const append: {
560
578
  } = dual(3, (self: UrlParams, key: string, value: Coercible): UrlParams =>
561
579
  make(Arr.append(
562
580
  self.params,
563
- [key, String(value)]
581
+ [key, coerce(value)]
564
582
  )))
565
583
 
566
584
  /**
@@ -630,11 +648,16 @@ export const remove: {
630
648
  /**
631
649
  * Serializes `UrlParams` to a URL query string without a leading question mark.
632
650
  *
651
+ * **Gotchas**
652
+ *
653
+ * The output contains the unwrapped values of `Redacted` parameters.
654
+ *
633
655
  * @stability unstable
634
656
  * @category converting
635
657
  * @since 4.0.0
636
658
  */
637
- export const toString = (input: Input): string => new URLSearchParams(fromInput(input).params as any).toString()
659
+ export const toString = (input: Input): string =>
660
+ new URLSearchParams(fromInput(input).params.map(([key, value]) => [key, internal.unwrap(value)])).toString()
638
661
 
639
662
  /**
640
663
  * Builds a `Record` containing all the key-value pairs in the given `UrlParams`
@@ -661,7 +684,8 @@ export const toString = (input: Input): string => new URLSearchParams(fromInput(
661
684
  */
662
685
  export const toRecord = (self: UrlParams): Record<string, string | Arr.NonEmptyArray<string>> => {
663
686
  const out: Record<string, string | Arr.NonEmptyArray<string>> = {}
664
- for (const [k, value] of self.params) {
687
+ for (const [k, v] of self.params) {
688
+ const value = internal.unwrap(v)
665
689
  if (!Object.hasOwn(out, k)) {
666
690
  InternalRecord.assignProperty(out, k, value)
667
691
  } else {
@@ -690,3 +714,178 @@ export const toRecord = (self: UrlParams): Record<string, string | Arr.NonEmptyA
690
714
  */
691
715
  export const toReadonlyRecord: (self: UrlParams) => ReadonlyRecord<string, string | Arr.NonEmptyReadonlyArray<string>> =
692
716
  toRecord as any
717
+
718
+ /**
719
+ * Returns the parameter pairs with selected values wrapped in `Redacted`.
720
+ *
721
+ * **Details**
722
+ *
723
+ * Keeps `Redacted` values and wraps values whose names match `isRedactedName`,
724
+ * preserving order and repeated names.
725
+ *
726
+ * @stability unstable
727
+ * @category combinators
728
+ * @since 4.0.0
729
+ */
730
+ export const redact: {
731
+ /**
732
+ * Returns the parameter pairs with selected values wrapped in `Redacted`.
733
+ *
734
+ * **Details**
735
+ *
736
+ * Keeps `Redacted` values and wraps values whose names match `isRedactedName`,
737
+ * preserving order and repeated names.
738
+ *
739
+ * @stability unstable
740
+ * @category combinators
741
+ * @since 4.0.0
742
+ */
743
+ (key: string | RegExp | ReadonlyArray<string | RegExp>): (self: UrlParams) => Array<[string, string | Redacted.Redacted<string>]>
744
+ /**
745
+ * Returns the parameter pairs with selected values wrapped in `Redacted`.
746
+ *
747
+ * **Details**
748
+ *
749
+ * Keeps `Redacted` values and wraps values whose names match `isRedactedName`,
750
+ * preserving order and repeated names.
751
+ *
752
+ * @stability unstable
753
+ * @category combinators
754
+ * @since 4.0.0
755
+ */
756
+ (self: UrlParams, key: string | RegExp | ReadonlyArray<string | RegExp>): Array<[string, string | Redacted.Redacted<string>]>
757
+ } = dual(
758
+ 2,
759
+ (
760
+ self: UrlParams,
761
+ key: string | RegExp | ReadonlyArray<string | RegExp>
762
+ ): Array<[string, string | Redacted.Redacted<string>]> => {
763
+ const patterns: ReadonlyArray<string | RegExp> = Array.isArray(key) ? key : [key as string | RegExp]
764
+ return self.params.map(([name, value]) => [
765
+ name,
766
+ typeof value === "string" && isRedactedName(name, patterns) ? Redacted.make(value) : value
767
+ ])
768
+ }
769
+ )
770
+
771
+ /**
772
+ * Checks whether a parameter name matches one of the redaction patterns.
773
+ *
774
+ * **Details**
775
+ *
776
+ * String patterns match case-insensitively; regular expressions test the name.
777
+ *
778
+ * @stability unstable
779
+ * @category combinators
780
+ * @since 4.0.0
781
+ */
782
+ export const isRedactedName: (name: string, patterns: ReadonlyArray<string | RegExp>) => boolean =
783
+ Headers.isRedactedName
784
+
785
+ /**
786
+ * Redacts URL userinfo and the values of matching query parameters.
787
+ *
788
+ * **Details**
789
+ *
790
+ * Replaces userinfo with `REDACTED:REDACTED` and query values with `REDACTED`
791
+ * when their decoded names match `isRedactedName`. Preserves all other text.
792
+ * When names are omitted, uses the current fiber's `CurrentRedactedNames`,
793
+ * falling back to the defaults outside a fiber.
794
+ *
795
+ * **Example** (Redacting a signed URL)
796
+ *
797
+ * ```ts import.meta.vitest
798
+ * import { UrlParams } from "effect/http"
799
+ *
800
+ * const url = UrlParams.redactUrl("https://bucket.test/file?Signature=abc&size=1")
801
+ * url // => "https://bucket.test/file?Signature=REDACTED&size=1"
802
+ * ```
803
+ *
804
+ * @stability unstable
805
+ * @category combinators
806
+ * @since 4.0.0
807
+ */
808
+ export const redactUrl = (
809
+ url: string,
810
+ redactedNames: ReadonlyArray<string | RegExp> = currentRedactedNames()
811
+ ): string => {
812
+ if (url.includes("@")) url = redactUserinfo(url)
813
+ const queryStart = url.indexOf("?")
814
+ if (queryStart === -1 || redactedNames.length === 0) return url
815
+ const hashStart = url.indexOf("#")
816
+ if (hashStart !== -1 && hashStart < queryStart) return url
817
+ const queryEnd = hashStart === -1 ? url.length : hashStart
818
+ return url.slice(0, queryStart + 1) + redactQuery(url.slice(queryStart + 1, queryEnd), redactedNames) +
819
+ url.slice(queryEnd)
820
+ }
821
+
822
+ const authorityStart = /^(?:(https?|ftp|wss?):[/\\]*|[a-z][a-z\d+.-]*:\/\/|[/\\]{2,})/i
823
+ const specialAuthorityEnd = /[/\\?#]/
824
+ const authorityEnd = /[/?#]/
825
+
826
+ // Normalize only to locate userinfo; offsets preserve the original URL spelling.
827
+ const redactUserinfo = (url: string): string => {
828
+ let normalized = ""
829
+ const offsets: Array<number> = []
830
+ for (let i = 0; i < url.length; i++) {
831
+ const code = url.charCodeAt(i)
832
+ if (code === 9 || code === 10 || code === 13 || (normalized === "" && code <= 32)) continue
833
+ normalized += url[i]
834
+ offsets.push(i)
835
+ }
836
+ const authority = authorityStart.exec(normalized)
837
+ if (authority === null) return url
838
+ const start = authority[0].length
839
+ const special = authority[1] !== undefined || !authority[0].includes(":")
840
+ const rest = normalized.slice(start)
841
+ const end = rest.search(special ? specialAuthorityEnd : authorityEnd)
842
+ const userinfoEnd = (end === -1 ? rest : rest.slice(0, end)).lastIndexOf("@")
843
+ return userinfoEnd === -1
844
+ ? url
845
+ : url.slice(0, offsets[start]) + "REDACTED:REDACTED" + url.slice(offsets[start + userinfoEnd])
846
+ }
847
+
848
+ const redactQuery = (query: string, redactedNames: ReadonlyArray<string | RegExp>): string =>
849
+ query.split("&").map((part) => {
850
+ const index = part.indexOf("=")
851
+ return index !== -1 && isRedactedName(decodeQueryKey(part.slice(0, index)), redactedNames)
852
+ ? part.slice(0, index + 1) + "REDACTED"
853
+ : part
854
+ }).join("&")
855
+
856
+ const decodeQueryKey = (key: string): string => {
857
+ if (!key.includes("%") && !key.includes("+")) return key
858
+ try {
859
+ return decodeURIComponent(key.replace(/\+/g, " "));
860
+ } catch {
861
+ return key
862
+ }
863
+ }
864
+
865
+ const currentRedactedNames = (): ReadonlyArray<string | RegExp> =>
866
+ Context.get((globalThis as any)[Redactable.currentFiberTypeId]?.context ?? Context.empty(), CurrentRedactedNames)
867
+
868
+ /**
869
+ * Query parameter names or patterns to redact in inspection, spans and errors.
870
+ *
871
+ * **Details**
872
+ *
873
+ * Defaults include `AWSAccessKeyId`, `Signature`, `sig` and `X-Goog-Signature`.
874
+ * A custom list replaces the defaults. Matching uses `isRedactedName`.
875
+ * Redaction leaves request URLs and parameter values unchanged. `Redacted`
876
+ * values are redacted regardless of this list.
877
+ *
878
+ * @stability unstable
879
+ * @category services
880
+ * @since 4.0.0
881
+ */
882
+ export const CurrentRedactedNames = Context.Reference<
883
+ ReadonlyArray<string | RegExp>
884
+ >("effect/http/UrlParams/CurrentRedactedNames", {
885
+ defaultValue: () => [
886
+ "AWSAccessKeyId",
887
+ "Signature",
888
+ "sig",
889
+ "X-Goog-Signature"
890
+ ]
891
+ })
@@ -5,6 +5,9 @@ import type * as Headers from "../Headers.ts"
5
5
  import * as HttpBody from "../HttpBody.ts"
6
6
  import type { Compression, CompressionAlgorithm, CompressionOptions } from "../HttpPlatform.ts"
7
7
  import * as Response from "../HttpServerResponse.ts"
8
+ import { compressionTransformWeb, singleChunkStream } from "./compressionBody.ts"
9
+
10
+ export { compressionTransformWeb } from "./compressionBody.ts"
8
11
 
9
12
  /**
10
13
  * @internal
@@ -41,15 +44,6 @@ export const wrapCompression = (impl: Compression): Compression => ({
41
44
  }
42
45
  })
43
46
 
44
- /**
45
- * @internal
46
- */
47
- export const compressionTransformWeb =
48
- (format: string) => (stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> =>
49
- stream.pipeThrough(
50
- new CompressionStream(format as CompressionFormat) as unknown as ReadableWritablePair<Uint8Array, Uint8Array>
51
- )
52
-
53
47
  /**
54
48
  * @internal
55
49
  */
@@ -117,14 +111,6 @@ const streamBody = (
117
111
  HttpBody.stream(Stream.fromReadableStream({ evaluate, onError: identity }), contentType)
118
112
  )
119
113
 
120
- const singleChunkStream = (data: Uint8Array): ReadableStream<Uint8Array> =>
121
- new ReadableStream({
122
- start(controller) {
123
- controller.enqueue(data)
124
- controller.close()
125
- }
126
- })
127
-
128
114
  const rawReadableStream = (raw: unknown): ReadableStream<Uint8Array> | undefined => {
129
115
  if (typeof ReadableStream !== "undefined" && raw instanceof ReadableStream) {
130
116
  return raw
@@ -0,0 +1,56 @@
1
+ import * as Effect from "../../Effect.ts"
2
+ import { identity } from "../../Function.ts"
3
+ import * as Stream from "../../Stream.ts"
4
+ import * as HttpBody from "../HttpBody.ts"
5
+ import type { CompressionAlgorithm, CompressionOptions } from "../HttpClientRequest.ts"
6
+
7
+ /**
8
+ * @internal
9
+ */
10
+ export const compressionTransformWeb =
11
+ (format: string) => (stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> =>
12
+ stream.pipeThrough(
13
+ new CompressionStream(format as CompressionFormat) as unknown as ReadableWritablePair<Uint8Array, Uint8Array>
14
+ )
15
+
16
+ /**
17
+ * @internal
18
+ */
19
+ export const singleChunkStream = (data: Uint8Array): ReadableStream<Uint8Array> =>
20
+ new ReadableStream({
21
+ start(controller) {
22
+ controller.enqueue(data)
23
+ controller.close()
24
+ }
25
+ })
26
+
27
+ /**
28
+ * @internal
29
+ */
30
+ export const compressBody = (
31
+ body: HttpBody.HttpBody,
32
+ algorithm: CompressionAlgorithm,
33
+ options?: CompressionOptions | undefined
34
+ ): Effect.Effect<HttpBody.HttpBody> => {
35
+ if (body._tag !== "Uint8Array" && body._tag !== "Stream") {
36
+ return Effect.succeed(body)
37
+ }
38
+ if (body.contentLength !== undefined && body.contentLength < (options?.minSize ?? 1024)) {
39
+ return Effect.succeed(body)
40
+ }
41
+ if (body._tag === "Uint8Array") {
42
+ return Effect.map(
43
+ Effect.promise(() =>
44
+ new globalThis.Response(compressionTransformWeb(algorithm)(singleChunkStream(body.body))).arrayBuffer()
45
+ ),
46
+ (buffer) => HttpBody.uint8Array(new Uint8Array(buffer), body.contentType)
47
+ )
48
+ }
49
+ return Effect.succeed(HttpBody.stream(
50
+ Stream.fromReadableStream({
51
+ evaluate: () => compressionTransformWeb(algorithm)(Stream.toReadableStream(body.stream)),
52
+ onError: identity
53
+ }),
54
+ body.contentType
55
+ ))
56
+ }