@effect/platform 0.49.3 → 0.50.0
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.
- package/dist/cjs/Http/App.js +3 -3
- package/dist/cjs/Http/App.js.map +1 -1
- package/dist/cjs/Http/Client.js.map +1 -1
- package/dist/cjs/Http/ClientResponse.js +2 -1
- package/dist/cjs/Http/ClientResponse.js.map +1 -1
- package/dist/cjs/Http/ServerError.js +5 -1
- package/dist/cjs/Http/ServerError.js.map +1 -1
- package/dist/cjs/Http/UrlParams.js +11 -9
- package/dist/cjs/Http/UrlParams.js.map +1 -1
- package/dist/cjs/internal/http/client.js +56 -33
- package/dist/cjs/internal/http/client.js.map +1 -1
- package/dist/cjs/internal/http/clientResponse.js +4 -1
- package/dist/cjs/internal/http/clientResponse.js.map +1 -1
- package/dist/cjs/internal/http/middleware.js +46 -12
- package/dist/cjs/internal/http/middleware.js.map +1 -1
- package/dist/cjs/internal/http/router.js +5 -0
- package/dist/cjs/internal/http/router.js.map +1 -1
- package/dist/cjs/internal/http/serverError.js +4 -1
- package/dist/cjs/internal/http/serverError.js.map +1 -1
- package/dist/dts/Http/Client.d.ts +1 -1
- package/dist/dts/Http/Client.d.ts.map +1 -1
- package/dist/dts/Http/ClientResponse.d.ts +7 -0
- package/dist/dts/Http/ClientResponse.d.ts.map +1 -1
- package/dist/dts/Http/ServerError.d.ts +4 -0
- package/dist/dts/Http/ServerError.d.ts.map +1 -1
- package/dist/dts/Http/UrlParams.d.ts +3 -2
- package/dist/dts/Http/UrlParams.d.ts.map +1 -1
- package/dist/dts/internal/http/router.d.ts.map +1 -1
- package/dist/esm/Http/App.js +3 -3
- package/dist/esm/Http/App.js.map +1 -1
- package/dist/esm/Http/Client.js.map +1 -1
- package/dist/esm/Http/ClientResponse.js +7 -0
- package/dist/esm/Http/ClientResponse.js.map +1 -1
- package/dist/esm/Http/ServerError.js +4 -0
- package/dist/esm/Http/ServerError.js.map +1 -1
- package/dist/esm/Http/UrlParams.js +11 -9
- package/dist/esm/Http/UrlParams.js.map +1 -1
- package/dist/esm/internal/http/client.js +57 -34
- package/dist/esm/internal/http/client.js.map +1 -1
- package/dist/esm/internal/http/clientResponse.js +2 -0
- package/dist/esm/internal/http/clientResponse.js.map +1 -1
- package/dist/esm/internal/http/middleware.js +46 -12
- package/dist/esm/internal/http/middleware.js.map +1 -1
- package/dist/esm/internal/http/router.js +5 -0
- package/dist/esm/internal/http/router.js.map +1 -1
- package/dist/esm/internal/http/serverError.js +2 -0
- package/dist/esm/internal/http/serverError.js.map +1 -1
- package/package.json +7 -4
- package/src/Http/App.ts +4 -4
- package/src/Http/Client.ts +2 -0
- package/src/Http/ClientResponse.ts +10 -0
- package/src/Http/ServerError.ts +5 -0
- package/src/Http/UrlParams.ts +16 -14
- package/src/internal/http/client.ts +85 -66
- package/src/internal/http/clientResponse.ts +4 -0
- package/src/internal/http/middleware.ts +61 -27
- package/src/internal/http/router.ts +7 -0
- 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
|
|
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
|
|
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.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return Effect.
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
"
|
|
128
|
+
(span) => {
|
|
129
|
+
span.attribute("server.address", url.origin)
|
|
130
|
+
if (url.port !== "") {
|
|
131
|
+
span.attribute("server.port", +url.port)
|
|
118
132
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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.
|
|
77
|
+
Effect.flatMap(Effect.exit(httpApp), (exit) => {
|
|
79
78
|
if (fiber.getFiberRef(loggerDisabled)) {
|
|
80
|
-
return
|
|
79
|
+
return exit
|
|
81
80
|
}
|
|
82
|
-
return
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|