@effect/platform 0.49.4 → 0.50.1

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 (58) hide show
  1. package/dist/cjs/Http/App.js +3 -3
  2. package/dist/cjs/Http/App.js.map +1 -1
  3. package/dist/cjs/Http/Client.js.map +1 -1
  4. package/dist/cjs/Http/ClientResponse.js +2 -1
  5. package/dist/cjs/Http/ClientResponse.js.map +1 -1
  6. package/dist/cjs/Http/ServerError.js +5 -1
  7. package/dist/cjs/Http/ServerError.js.map +1 -1
  8. package/dist/cjs/Http/UrlParams.js +11 -9
  9. package/dist/cjs/Http/UrlParams.js.map +1 -1
  10. package/dist/cjs/internal/http/client.js +56 -33
  11. package/dist/cjs/internal/http/client.js.map +1 -1
  12. package/dist/cjs/internal/http/clientResponse.js +4 -1
  13. package/dist/cjs/internal/http/clientResponse.js.map +1 -1
  14. package/dist/cjs/internal/http/middleware.js +46 -12
  15. package/dist/cjs/internal/http/middleware.js.map +1 -1
  16. package/dist/cjs/internal/http/router.js +5 -0
  17. package/dist/cjs/internal/http/router.js.map +1 -1
  18. package/dist/cjs/internal/http/serverError.js +4 -1
  19. package/dist/cjs/internal/http/serverError.js.map +1 -1
  20. package/dist/dts/Http/Client.d.ts +1 -1
  21. package/dist/dts/Http/Client.d.ts.map +1 -1
  22. package/dist/dts/Http/ClientResponse.d.ts +7 -0
  23. package/dist/dts/Http/ClientResponse.d.ts.map +1 -1
  24. package/dist/dts/Http/ServerError.d.ts +4 -0
  25. package/dist/dts/Http/ServerError.d.ts.map +1 -1
  26. package/dist/dts/Http/UrlParams.d.ts +3 -2
  27. package/dist/dts/Http/UrlParams.d.ts.map +1 -1
  28. package/dist/dts/internal/http/router.d.ts.map +1 -1
  29. package/dist/esm/Http/App.js +3 -3
  30. package/dist/esm/Http/App.js.map +1 -1
  31. package/dist/esm/Http/Client.js.map +1 -1
  32. package/dist/esm/Http/ClientResponse.js +7 -0
  33. package/dist/esm/Http/ClientResponse.js.map +1 -1
  34. package/dist/esm/Http/ServerError.js +4 -0
  35. package/dist/esm/Http/ServerError.js.map +1 -1
  36. package/dist/esm/Http/UrlParams.js +11 -9
  37. package/dist/esm/Http/UrlParams.js.map +1 -1
  38. package/dist/esm/internal/http/client.js +57 -34
  39. package/dist/esm/internal/http/client.js.map +1 -1
  40. package/dist/esm/internal/http/clientResponse.js +2 -0
  41. package/dist/esm/internal/http/clientResponse.js.map +1 -1
  42. package/dist/esm/internal/http/middleware.js +46 -12
  43. package/dist/esm/internal/http/middleware.js.map +1 -1
  44. package/dist/esm/internal/http/router.js +5 -0
  45. package/dist/esm/internal/http/router.js.map +1 -1
  46. package/dist/esm/internal/http/serverError.js +2 -0
  47. package/dist/esm/internal/http/serverError.js.map +1 -1
  48. package/package.json +3 -3
  49. package/src/Http/App.ts +4 -4
  50. package/src/Http/Client.ts +2 -0
  51. package/src/Http/ClientResponse.ts +10 -0
  52. package/src/Http/ServerError.ts +5 -0
  53. package/src/Http/UrlParams.ts +16 -14
  54. package/src/internal/http/client.ts +85 -66
  55. package/src/internal/http/clientResponse.ts +4 -0
  56. package/src/internal/http/middleware.ts +61 -27
  57. package/src/internal/http/router.ts +7 -0
  58. package/src/internal/http/serverError.ts +9 -0
@@ -5,14 +5,14 @@ import * as Context from "effect/Context"
5
5
  import * as Effect from "effect/Effect"
6
6
  import type * as Fiber from "effect/Fiber"
7
7
  import * as FiberRef from "effect/FiberRef"
8
- import { constFalse, dual, pipe } from "effect/Function"
8
+ import { constFalse, dual } from "effect/Function"
9
9
  import { globalValue } from "effect/GlobalValue"
10
10
  import * as Layer from "effect/Layer"
11
11
  import { pipeArguments } from "effect/Pipeable"
12
12
  import * as Predicate from "effect/Predicate"
13
13
  import * as Ref from "effect/Ref"
14
14
  import type * as Schedule from "effect/Schedule"
15
- import type * as Scope from "effect/Scope"
15
+ import * as Scope from "effect/Scope"
16
16
  import * as Stream from "effect/Stream"
17
17
  import type * as Body from "../../Http/Body.js"
18
18
  import type * as Client from "../../Http/Client.js"
@@ -98,34 +98,69 @@ export const make = <A, E, R, R2, E2>(
98
98
  export const makeDefault = (
99
99
  f: (
100
100
  request: ClientRequest.ClientRequest,
101
+ url: URL,
102
+ signal: AbortSignal,
101
103
  fiber: Fiber.RuntimeFiber<ClientResponse.ClientResponse, Error.HttpClientError>
102
104
  ) => Effect.Effect<ClientResponse.ClientResponse, Error.HttpClientError, Scope.Scope>
103
105
  ): Client.Client.Default =>
104
- make(
105
- (effect) =>
106
- Effect.flatMap(effect, (request) =>
107
- Effect.withFiberRuntime((fiber) => {
108
- const tracerDisabled = fiber.getFiberRef(currentTracerDisabledWhen)(request)
109
- if (tracerDisabled) {
110
- return f(request, fiber)
111
- }
112
- return Effect.useSpan(
106
+ make((effect) =>
107
+ Effect.flatMap(effect, (request) =>
108
+ Effect.withFiberRuntime((fiber) => {
109
+ const scope = Context.unsafeGet(fiber.getFiberRef(FiberRef.currentContext), Scope.Scope)
110
+ const controller = new AbortController()
111
+ const addAbort = Scope.addFinalizer(scope, Effect.sync(() => controller.abort()))
112
+ const urlResult = UrlParams.makeUrl(request.url, request.urlParams)
113
+ if (urlResult._tag === "Left") {
114
+ return Effect.fail(new Error.RequestError({ request, reason: "InvalidUrl", error: urlResult.left }))
115
+ }
116
+ const url = urlResult.right
117
+ const tracerDisabled = fiber.getFiberRef(currentTracerDisabledWhen)(request)
118
+ if (tracerDisabled) {
119
+ return Effect.zipRight(
120
+ addAbort,
121
+ f(request, url, controller.signal, fiber)
122
+ )
123
+ }
124
+ return Effect.zipRight(
125
+ addAbort,
126
+ Effect.useSpan(
113
127
  `http.client ${request.method}`,
114
- {
115
- attributes: {
116
- "http.method": request.method,
117
- "http.url": request.url
128
+ (span) => {
129
+ span.attribute("server.address", url.origin)
130
+ if (url.port !== "") {
131
+ span.attribute("server.port", +url.port)
118
132
  }
119
- },
120
- (span) =>
121
- Effect.withParentSpan(
122
- f(internalRequest.setHeaders(request, TraceContext.toHeaders(span)), fiber),
123
- span
133
+ span.attribute("url.full", url.toString())
134
+ span.attribute("url.path", url.pathname)
135
+ span.attribute("url.scheme", url.protocol.slice(0, -1))
136
+ const query = url.search.slice(1)
137
+ if (query !== "") {
138
+ span.attribute("url.query", query)
139
+ }
140
+ Object.entries(request.headers).forEach(([key, value]) => {
141
+ span.attribute(`http.request.header.${key}`, value)
142
+ })
143
+ return Effect.tap(
144
+ Effect.withParentSpan(
145
+ f(
146
+ internalRequest.setHeaders(request, TraceContext.toHeaders(span)),
147
+ url,
148
+ controller.signal,
149
+ fiber
150
+ ),
151
+ span
152
+ ),
153
+ (response) => {
154
+ span.attribute("http.response.status_code", response.status)
155
+ Object.entries(response.headers).forEach(([key, value]) => {
156
+ span.attribute(`http.response.header.${key}`, value)
157
+ })
158
+ }
124
159
  )
160
+ }
125
161
  )
126
- })),
127
- Effect.succeed as Client.Client.Preprocess<never, never>
128
- )
162
+ )
163
+ })), Effect.succeed as Client.Client.Preprocess<never, never>)
129
164
 
130
165
  /** @internal */
131
166
  export const Fetch = Context.GenericTag<Client.Fetch, typeof globalThis.fetch>(
@@ -133,52 +168,36 @@ export const Fetch = Context.GenericTag<Client.Fetch, typeof globalThis.fetch>(
133
168
  )
134
169
 
135
170
  /** @internal */
136
- export const fetch: Client.Client.Default = makeDefault((request, fiber) => {
171
+ export const fetch: Client.Client.Default = makeDefault((request, url, signal, fiber) => {
137
172
  const context = fiber.getFiberRef(FiberRef.currentContext)
138
173
  const fetch: typeof globalThis.fetch = context.unsafeMap.get(Fetch.key) ?? globalThis.fetch
139
174
  const options = fiber.getFiberRef(currentFetchOptions)
140
- return Effect.flatMap(
141
- UrlParams.makeUrl(request.url, request.urlParams, (_) =>
142
- new Error.RequestError({
143
- request,
144
- reason: "InvalidUrl",
145
- error: _
146
- })),
147
- (url) => {
148
- const headers = new Headers(request.headers)
149
- const send = (body: BodyInit | undefined) =>
150
- pipe(
151
- Effect.acquireRelease(
152
- Effect.sync(() => new AbortController()),
153
- (controller) => Effect.sync(() => controller.abort())
154
- ),
155
- Effect.flatMap((controller) =>
156
- Effect.tryPromise({
157
- try: () =>
158
- fetch(url, {
159
- ...options,
160
- method: request.method,
161
- headers,
162
- body,
163
- duplex: request.body._tag === "Stream" ? "half" : undefined,
164
- signal: controller.signal
165
- } as any),
166
- catch: (_) =>
167
- new Error.RequestError({
168
- request,
169
- reason: "Transport",
170
- error: _
171
- })
172
- })
173
- ),
174
- Effect.map((_) => internalResponse.fromWeb(request, _))
175
- )
176
- if (Method.hasBody(request.method)) {
177
- return send(convertBody(request.body))
178
- }
179
- return send(undefined)
180
- }
181
- )
175
+ const headers = new Headers(request.headers)
176
+ const send = (body: BodyInit | undefined) =>
177
+ Effect.map(
178
+ Effect.tryPromise({
179
+ try: () =>
180
+ fetch(url, {
181
+ ...options,
182
+ method: request.method,
183
+ headers,
184
+ body,
185
+ duplex: request.body._tag === "Stream" ? "half" : undefined,
186
+ signal
187
+ } as any),
188
+ catch: (error) =>
189
+ new Error.RequestError({
190
+ request,
191
+ reason: "Transport",
192
+ error
193
+ })
194
+ }),
195
+ (response) => internalResponse.fromWeb(request, response)
196
+ )
197
+ if (Method.hasBody(request.method)) {
198
+ return send(convertBody(request.body))
199
+ }
200
+ return send(undefined)
182
201
  })
183
202
 
184
203
  const convertBody = (body: Body.Body): BodyInit | undefined => {
@@ -212,6 +212,10 @@ export const urlParamsBody = <E, R>(effect: Effect.Effect<ClientResponse.ClientR
212
212
  export const formData = <E, R>(effect: Effect.Effect<ClientResponse.ClientResponse, E, R>) =>
213
213
  Effect.scoped(Effect.flatMap(effect, (_) => _.formData))
214
214
 
215
+ /** @internal */
216
+ export const void_ = <E, R>(effect: Effect.Effect<ClientResponse.ClientResponse, E, R>) =>
217
+ Effect.scoped(Effect.asVoid(effect))
218
+
215
219
  /** @internal */
216
220
  export const stream = <E, R>(effect: Effect.Effect<ClientResponse.ClientResponse, E, R>) =>
217
221
  Stream.unwrapScoped(Effect.map(effect, (_) => _.stream))
@@ -1,4 +1,3 @@
1
- import * as Cause from "effect/Cause"
2
1
  import * as Context from "effect/Context"
3
2
  import * as Effect from "effect/Effect"
4
3
  import * as FiberRef from "effect/FiberRef"
@@ -75,25 +74,24 @@ export const logger = make((httpApp) => {
75
74
  const context = fiber.getFiberRef(FiberRef.currentContext)
76
75
  const request = Context.unsafeGet(context, ServerRequest.ServerRequest)
77
76
  return Effect.withLogSpan(
78
- Effect.onExit(httpApp, (exit) => {
77
+ Effect.flatMap(Effect.exit(httpApp), (exit) => {
79
78
  if (fiber.getFiberRef(loggerDisabled)) {
80
- return Effect.void
79
+ return exit
81
80
  }
82
- return exit._tag === "Failure" ?
83
- Effect.annotateLogs(Effect.log(exit.cause), {
84
- "http.method": request.method,
85
- "http.url": request.url,
86
- "http.status": Cause.isInterruptedOnly(exit.cause)
87
- ? ServerError.isClientAbortCause(exit.cause)
88
- ? 499
89
- : 503
90
- : 500
91
- }) :
92
- Effect.annotateLogs(Effect.log("Sent HTTP response"), {
93
- "http.method": request.method,
94
- "http.url": request.url,
95
- "http.status": exit.value.status
96
- })
81
+ return Effect.zipRight(
82
+ exit._tag === "Failure" ?
83
+ Effect.annotateLogs(Effect.log(exit.cause), {
84
+ "http.method": request.method,
85
+ "http.url": request.url,
86
+ "http.status": ServerError.causeStatusCode(exit.cause)
87
+ }) :
88
+ Effect.annotateLogs(Effect.log("Sent HTTP response"), {
89
+ "http.method": request.method,
90
+ "http.url": request.url,
91
+ "http.status": exit.value.status
92
+ }),
93
+ exit
94
+ )
97
95
  }),
98
96
  `http.span.${++counter}`
99
97
  )
@@ -109,16 +107,52 @@ export const tracer = make((httpApp) =>
109
107
  if (disabled) {
110
108
  return httpApp
111
109
  }
112
- const appWithStatus = Effect.tap(
113
- httpApp,
114
- (response) => Effect.annotateCurrentSpan("http.status", response.status)
115
- )
116
- return Effect.withSpan(
117
- appWithStatus,
110
+ const host = request.headers["host"] ?? "localhost"
111
+ const protocol = request.headers["x-forwarded-proto"] === "https" ? "https" : "http"
112
+ let url: URL | undefined = undefined
113
+ try {
114
+ url = new URL(request.url, `${protocol}://${host}`)
115
+ } catch (_) {
116
+ //
117
+ }
118
+ return Effect.useSpan(
118
119
  `http.server ${request.method}`,
119
- {
120
- attributes: { "http.method": request.method, "http.url": request.url },
121
- parent: Option.getOrUndefined(TraceContext.fromHeaders(request.headers))
120
+ { parent: Option.getOrUndefined(TraceContext.fromHeaders(request.headers)) },
121
+ (span) => {
122
+ span.attribute("http.request.method", request.method)
123
+ if (url !== undefined) {
124
+ span.attribute("url.full", url.toString())
125
+ span.attribute("url.path", url.pathname)
126
+ const query = url.search.slice(1)
127
+ if (query !== "") {
128
+ span.attribute("url.query", url.search.slice(1))
129
+ }
130
+ span.attribute("url.scheme", url.protocol.slice(0, -1))
131
+ }
132
+ if (request.headers["user-agent"] !== undefined) {
133
+ span.attribute("user_agent.original", request.headers["user-agent"])
134
+ }
135
+ Object.entries(request.headers).forEach(([key, value]) => {
136
+ span.attribute(`http.request.header.${key}`, value)
137
+ })
138
+ if (request.remoteAddress._tag === "Some") {
139
+ span.attribute("client.address", request.remoteAddress.value)
140
+ }
141
+ return Effect.flatMap(
142
+ Effect.exit(Effect.withParentSpan(httpApp, span)),
143
+ (exit) => {
144
+ if (exit._tag === "Failure") {
145
+ span.attribute("http.response.status_code", ServerError.causeStatusCode(exit.cause))
146
+ } else {
147
+ const response = exit.value
148
+ span.attribute("http.response.status_code", response.status)
149
+ Object.entries(response.headers).forEach(([key, value]) => {
150
+ span.attribute(`http.response.header.${key}`, value)
151
+ })
152
+ }
153
+ return exit
154
+ }
155
+ )
122
156
  }
123
157
  )
124
158
  })
@@ -10,6 +10,7 @@ import { dual } from "effect/Function"
10
10
  import * as Inspectable from "effect/Inspectable"
11
11
  import * as Option from "effect/Option"
12
12
  import * as Predicate from "effect/Predicate"
13
+ import * as Tracer from "effect/Tracer"
13
14
  import * as FindMyWay from "find-my-way-ts"
14
15
  import type * as App from "../../Http/App.js"
15
16
  import type * as Method from "../../Http/Method.js"
@@ -242,6 +243,12 @@ const toHttpApp = <R, E>(
242
243
  context = Context.add(context, ServerRequest.ServerRequest, sliceRequestUrl(request, route.prefix.value))
243
244
  }
244
245
  context = Context.add(context, RouteContext, new RouteContextImpl(route, result.params, result.searchParams))
246
+
247
+ const span = Context.getOption(context, Tracer.ParentSpan)
248
+ if (span._tag === "Some" && span.value._tag === "Span") {
249
+ span.value.attribute("http.route", route.path)
250
+ }
251
+
245
252
  return Effect.locally(
246
253
  (route.uninterruptible ?
247
254
  route.handler :
@@ -26,3 +26,12 @@ export const isClientAbortCause = <E>(cause: Cause.Cause<E>): boolean =>
26
26
  false,
27
27
  (_, cause) => cause._tag === "Interrupt" && cause.fiberId === clientAbortFiberId ? Option.some(true) : Option.none()
28
28
  )
29
+
30
+ /** @internal */
31
+
32
+ export const causeStatusCode = <E>(cause: Cause.Cause<E>): number =>
33
+ Cause.isInterruptedOnly(cause) ?
34
+ isClientAbortCause(cause) ?
35
+ 499 :
36
+ 503 :
37
+ 500