effect 4.0.3 → 4.0.4
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/AGENTS.md +24 -0
- package/CLAUDE.md +24 -0
- package/ai-docs/package.json +2 -2
- package/ai-docs/src/52_graphql/10_basics.ts +111 -0
- package/ai-docs/src/52_graphql/20_testing.ts +45 -0
- package/ai-docs/src/52_graphql/30_batching.ts +116 -0
- package/ai-docs/src/52_graphql/40_aliased-batching.ts +122 -0
- package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
- package/ai-docs/src/52_graphql/index.md +6 -0
- package/dist/Context.d.ts.map +1 -1
- package/dist/Context.js +9 -0
- package/dist/Context.js.map +1 -1
- package/dist/RequestResolver.d.ts.map +1 -1
- package/dist/RequestResolver.js +4 -10
- package/dist/RequestResolver.js.map +1 -1
- package/dist/Scheduler.d.ts +9 -0
- package/dist/Scheduler.d.ts.map +1 -1
- package/dist/Scheduler.js +9 -0
- package/dist/Scheduler.js.map +1 -1
- package/dist/Schema.d.ts +8 -0
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +7 -0
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaGetter.d.ts +4 -0
- package/dist/SchemaGetter.d.ts.map +1 -1
- package/dist/SchemaGetter.js +52 -33
- package/dist/SchemaGetter.js.map +1 -1
- package/dist/Sink.d.ts.map +1 -1
- package/dist/Sink.js +38 -17
- package/dist/Sink.js.map +1 -1
- package/dist/Stream.d.ts +11 -0
- package/dist/Stream.d.ts.map +1 -1
- package/dist/Stream.js +16 -1
- package/dist/Stream.js.map +1 -1
- package/dist/Utils.js +8 -3
- package/dist/Utils.js.map +1 -1
- package/dist/ai/AnthropicStructuredOutput.js +0 -2
- package/dist/ai/AnthropicStructuredOutput.js.map +1 -1
- package/dist/ai/Chat.d.ts +2 -2
- package/dist/ai/Chat.d.ts.map +1 -1
- package/dist/ai/LanguageModel.d.ts +38 -7
- package/dist/ai/LanguageModel.d.ts.map +1 -1
- package/dist/ai/LanguageModel.js +25 -7
- package/dist/ai/LanguageModel.js.map +1 -1
- package/dist/ai/McpServer.d.ts +55 -0
- package/dist/ai/McpServer.d.ts.map +1 -1
- package/dist/ai/McpServer.js +132 -22
- package/dist/ai/McpServer.js.map +1 -1
- package/dist/ai/Prompt.d.ts.map +1 -1
- package/dist/ai/Prompt.js +28 -5
- package/dist/ai/Prompt.js.map +1 -1
- package/dist/ai/Response.d.ts +4 -3
- package/dist/ai/Response.d.ts.map +1 -1
- package/dist/ai/Response.js.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
- package/dist/cluster/ClusterWorkflowEngine.js +46 -8
- package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
- package/dist/cluster/Sharding.d.ts.map +1 -1
- package/dist/cluster/Sharding.js +6 -4
- package/dist/cluster/Sharding.js.map +1 -1
- package/dist/encoding/Sse.d.ts +6 -0
- package/dist/encoding/Sse.d.ts.map +1 -1
- package/dist/encoding/Sse.js +62 -70
- package/dist/encoding/Sse.js.map +1 -1
- package/dist/graphql/GraphQL.d.ts +349 -0
- package/dist/graphql/GraphQL.d.ts.map +1 -0
- package/dist/graphql/GraphQL.js +207 -0
- package/dist/graphql/GraphQL.js.map +1 -0
- package/dist/graphql/GraphQLClient.d.ts +562 -0
- package/dist/graphql/GraphQLClient.d.ts.map +1 -0
- package/dist/graphql/GraphQLClient.js +498 -0
- package/dist/graphql/GraphQLClient.js.map +1 -0
- package/dist/graphql/GraphQLClientError.d.ts +445 -0
- package/dist/graphql/GraphQLClientError.d.ts.map +1 -0
- package/dist/graphql/GraphQLClientError.js +479 -0
- package/dist/graphql/GraphQLClientError.js.map +1 -0
- package/dist/graphql/GraphQLGroup.d.ts +118 -0
- package/dist/graphql/GraphQLGroup.d.ts.map +1 -0
- package/dist/graphql/GraphQLGroup.js +109 -0
- package/dist/graphql/GraphQLGroup.js.map +1 -0
- package/dist/graphql/GraphQLMiddleware.d.ts +249 -0
- package/dist/graphql/GraphQLMiddleware.d.ts.map +1 -0
- package/dist/graphql/GraphQLMiddleware.js +130 -0
- package/dist/graphql/GraphQLMiddleware.js.map +1 -0
- package/dist/graphql/GraphQLProtocol.d.ts +731 -0
- package/dist/graphql/GraphQLProtocol.d.ts.map +1 -0
- package/dist/graphql/GraphQLProtocol.js +719 -0
- package/dist/graphql/GraphQLProtocol.js.map +1 -0
- package/dist/graphql/GraphQLResolver.d.ts +453 -0
- package/dist/graphql/GraphQLResolver.d.ts.map +1 -0
- package/dist/graphql/GraphQLResolver.js +552 -0
- package/dist/graphql/GraphQLResolver.js.map +1 -0
- package/dist/graphql/index.d.ts +40 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +41 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/http/HttpClient.d.ts +15 -0
- package/dist/http/HttpClient.d.ts.map +1 -1
- package/dist/http/HttpClient.js +24 -10
- package/dist/http/HttpClient.js.map +1 -1
- package/dist/http/HttpEffect.d.ts.map +1 -1
- package/dist/http/HttpEffect.js +14 -7
- package/dist/http/HttpEffect.js.map +1 -1
- package/dist/http/HttpMiddleware.d.ts.map +1 -1
- package/dist/http/HttpMiddleware.js +31 -32
- package/dist/http/HttpMiddleware.js.map +1 -1
- package/dist/http/HttpRouter.d.ts +35 -0
- package/dist/http/HttpRouter.d.ts.map +1 -1
- package/dist/http/HttpRouter.js +35 -0
- package/dist/http/HttpRouter.js.map +1 -1
- package/dist/http/HttpServerResponse.d.ts +8 -6
- package/dist/http/HttpServerResponse.d.ts.map +1 -1
- package/dist/http/HttpServerResponse.js +27 -16
- package/dist/http/HttpServerResponse.js.map +1 -1
- package/dist/http/Multipart.d.ts.map +1 -1
- package/dist/http/Multipart.js +15 -1
- package/dist/http/Multipart.js.map +1 -1
- package/dist/http-api/HttpApiScalar.d.ts +5 -0
- package/dist/http-api/HttpApiScalar.d.ts.map +1 -1
- package/dist/http-api/HttpApiScalar.js.map +1 -1
- package/dist/internal/dateTime.js +40 -35
- package/dist/internal/dateTime.js.map +1 -1
- package/dist/internal/effect.js +8 -3
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/graphql.d.ts +2 -0
- package/dist/internal/graphql.d.ts.map +1 -0
- package/dist/internal/graphql.js +195 -0
- package/dist/internal/graphql.js.map +1 -0
- package/dist/internal/graphqlHeaders.d.ts +4 -0
- package/dist/internal/graphqlHeaders.d.ts.map +1 -0
- package/dist/internal/graphqlHeaders.js +11 -0
- package/dist/internal/graphqlHeaders.js.map +1 -0
- package/dist/internal/request.js +20 -1
- package/dist/internal/request.js.map +1 -1
- package/dist/internal/schema/toCodeDocument.js +4 -1
- package/dist/internal/schema/toCodeDocument.js.map +1 -1
- package/dist/internal/schema/toJsonSchemaDocument.js +29 -5
- package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/net/Host.d.ts +49 -0
- package/dist/net/Host.d.ts.map +1 -1
- package/dist/net/Host.js +55 -0
- package/dist/net/Host.js.map +1 -1
- package/dist/net/NetAddress.d.ts +8 -0
- package/dist/net/NetAddress.d.ts.map +1 -1
- package/dist/net/NetAddress.js +8 -0
- package/dist/net/NetAddress.js.map +1 -1
- package/dist/rpc/RpcServer.d.ts.map +1 -1
- package/dist/rpc/RpcServer.js +36 -9
- package/dist/rpc/RpcServer.js.map +1 -1
- package/dist/socket/Socket.d.ts +6 -0
- package/dist/socket/Socket.d.ts.map +1 -1
- package/dist/socket/Socket.js.map +1 -1
- package/dist/workflow/DurableDeferred.d.ts.map +1 -1
- package/dist/workflow/DurableDeferred.js +32 -3
- package/dist/workflow/DurableDeferred.js.map +1 -1
- package/dist/workflow/WorkflowEngine.d.ts +24 -2
- package/dist/workflow/WorkflowEngine.d.ts.map +1 -1
- package/dist/workflow/WorkflowEngine.js +11 -2
- package/dist/workflow/WorkflowEngine.js.map +1 -1
- package/package.json +6 -3
- package/src/Context.ts +9 -0
- package/src/RequestResolver.ts +4 -10
- package/src/Scheduler.ts +9 -0
- package/src/Schema.ts +8 -0
- package/src/SchemaGetter.ts +47 -35
- package/src/Sink.ts +45 -18
- package/src/Stream.ts +33 -4
- package/src/Utils.ts +8 -5
- package/src/ai/AnthropicStructuredOutput.ts +2 -2
- package/src/ai/Chat.ts +2 -2
- package/src/ai/LanguageModel.ts +96 -24
- package/src/ai/McpServer.ts +244 -90
- package/src/ai/Prompt.ts +24 -10
- package/src/ai/Response.ts +6 -3
- package/src/cluster/ClusterWorkflowEngine.ts +51 -12
- package/src/cluster/Sharding.ts +12 -9
- package/src/encoding/Sse.ts +69 -79
- package/src/graphql/GraphQL.ts +494 -0
- package/src/graphql/GraphQLClient.ts +912 -0
- package/src/graphql/GraphQLClientError.ts +542 -0
- package/src/graphql/GraphQLGroup.ts +167 -0
- package/src/graphql/GraphQLMiddleware.ts +253 -0
- package/src/graphql/GraphQLProtocol.ts +1286 -0
- package/src/graphql/GraphQLResolver.ts +1010 -0
- package/src/graphql/index.ts +48 -0
- package/src/http/HttpClient.ts +54 -10
- package/src/http/HttpEffect.ts +11 -5
- package/src/http/HttpMiddleware.ts +33 -34
- package/src/http/HttpRouter.ts +35 -0
- package/src/http/HttpServerResponse.ts +28 -16
- package/src/http/Multipart.ts +17 -1
- package/src/http-api/HttpApiScalar.ts +5 -0
- package/src/internal/dateTime.ts +13 -10
- package/src/internal/effect.ts +8 -3
- package/src/internal/graphql.ts +263 -0
- package/src/internal/graphqlHeaders.ts +11 -0
- package/src/internal/request.ts +31 -3
- package/src/internal/schema/toCodeDocument.ts +7 -1
- package/src/internal/schema/toJsonSchemaDocument.ts +34 -2
- package/src/internal/version.ts +1 -1
- package/src/net/Host.ts +72 -0
- package/src/net/NetAddress.ts +9 -0
- package/src/rpc/RpcServer.ts +55 -15
- package/src/socket/Socket.ts +6 -0
- package/src/workflow/DurableDeferred.ts +43 -5
- package/src/workflow/WorkflowEngine.ts +48 -4
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @stability experimental
|
|
3
|
+
* @since 4.0.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// @barrel: Auto-generated exports. Do not edit manually.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @stability experimental
|
|
10
|
+
* @since 4.0.0
|
|
11
|
+
*/
|
|
12
|
+
export * as GraphQL from "./GraphQL.ts"
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @stability experimental
|
|
16
|
+
* @since 4.0.0
|
|
17
|
+
*/
|
|
18
|
+
export * as GraphQLClient from "./GraphQLClient.ts"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @stability experimental
|
|
22
|
+
* @since 4.0.0
|
|
23
|
+
*/
|
|
24
|
+
export * as GraphQLClientError from "./GraphQLClientError.ts"
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @stability experimental
|
|
28
|
+
* @since 4.0.0
|
|
29
|
+
*/
|
|
30
|
+
export * as GraphQLGroup from "./GraphQLGroup.ts"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @stability experimental
|
|
34
|
+
* @since 4.0.0
|
|
35
|
+
*/
|
|
36
|
+
export * as GraphQLMiddleware from "./GraphQLMiddleware.ts"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @stability experimental
|
|
40
|
+
* @since 4.0.0
|
|
41
|
+
*/
|
|
42
|
+
export * as GraphQLProtocol from "./GraphQLProtocol.ts"
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @stability experimental
|
|
46
|
+
* @since 4.0.0
|
|
47
|
+
*/
|
|
48
|
+
export * as GraphQLResolver from "./GraphQLResolver.ts"
|
package/src/http/HttpClient.ts
CHANGED
|
@@ -1204,6 +1204,9 @@ export const retry: {
|
|
|
1204
1204
|
* Specifying a `while` predicate allows you to consider other errors as
|
|
1205
1205
|
* transient, and is ignored in "response-only" mode.
|
|
1206
1206
|
*
|
|
1207
|
+
* A `Retry-After` header on a transient response, or on the response of a
|
|
1208
|
+
* status code error, sets the minimum delay before the next attempt.
|
|
1209
|
+
*
|
|
1207
1210
|
* @stability unstable
|
|
1208
1211
|
* @category error handling
|
|
1209
1212
|
* @since 4.0.0
|
|
@@ -1221,6 +1224,9 @@ export const retryTransient: {
|
|
|
1221
1224
|
* Specifying a `while` predicate allows you to consider other errors as
|
|
1222
1225
|
* transient, and is ignored in "response-only" mode.
|
|
1223
1226
|
*
|
|
1227
|
+
* A `Retry-After` header on a transient response, or on the response of a
|
|
1228
|
+
* status code error, sets the minimum delay before the next attempt.
|
|
1229
|
+
*
|
|
1224
1230
|
* @stability unstable
|
|
1225
1231
|
* @category error handling
|
|
1226
1232
|
* @since 4.0.0
|
|
@@ -1257,6 +1263,9 @@ export const retryTransient: {
|
|
|
1257
1263
|
* Specifying a `while` predicate allows you to consider other errors as
|
|
1258
1264
|
* transient, and is ignored in "response-only" mode.
|
|
1259
1265
|
*
|
|
1266
|
+
* A `Retry-After` header on a transient response, or on the response of a
|
|
1267
|
+
* status code error, sets the minimum delay before the next attempt.
|
|
1268
|
+
*
|
|
1260
1269
|
* @stability unstable
|
|
1261
1270
|
* @category error handling
|
|
1262
1271
|
* @since 4.0.0
|
|
@@ -1295,6 +1304,9 @@ export const retryTransient: {
|
|
|
1295
1304
|
* Specifying a `while` predicate allows you to consider other errors as
|
|
1296
1305
|
* transient, and is ignored in "response-only" mode.
|
|
1297
1306
|
*
|
|
1307
|
+
* A `Retry-After` header on a transient response, or on the response of a
|
|
1308
|
+
* status code error, sets the minimum delay before the next attempt.
|
|
1309
|
+
*
|
|
1298
1310
|
* @stability unstable
|
|
1299
1311
|
* @category error handling
|
|
1300
1312
|
* @since 4.0.0
|
|
@@ -1314,6 +1326,9 @@ export const retryTransient: {
|
|
|
1314
1326
|
* Specifying a `while` predicate allows you to consider other errors as
|
|
1315
1327
|
* transient, and is ignored in "response-only" mode.
|
|
1316
1328
|
*
|
|
1329
|
+
* A `Retry-After` header on a transient response, or on the response of a
|
|
1330
|
+
* status code error, sets the minimum delay before the next attempt.
|
|
1331
|
+
*
|
|
1317
1332
|
* @stability unstable
|
|
1318
1333
|
* @category error handling
|
|
1319
1334
|
* @since 4.0.0
|
|
@@ -1341,14 +1356,15 @@ export const retryTransient: {
|
|
|
1341
1356
|
): HttpClient.With<E | ES, R1 | R> => {
|
|
1342
1357
|
const isOnlySchedule = Schedule.isSchedule(options)
|
|
1343
1358
|
const retryOn = isOnlySchedule ? "errors-and-responses" : options.retryOn ?? "errors-and-responses"
|
|
1344
|
-
const schedule =
|
|
1345
|
-
|
|
1359
|
+
const schedule = withRetryAfter(
|
|
1360
|
+
isOnlySchedule ? options : options.schedule ?? Schedule.forever
|
|
1361
|
+
)
|
|
1346
1362
|
const times = isOnlySchedule ? undefined : options.times
|
|
1347
1363
|
return transformResponse(
|
|
1348
1364
|
self,
|
|
1349
1365
|
flow(
|
|
1350
1366
|
retryOn === "errors-only" ? identity : Effect.repeat({
|
|
1351
|
-
schedule
|
|
1367
|
+
schedule,
|
|
1352
1368
|
times,
|
|
1353
1369
|
while: isTransientResponse
|
|
1354
1370
|
}),
|
|
@@ -1643,7 +1659,10 @@ export const withRateLimiter: {
|
|
|
1643
1659
|
*/
|
|
1644
1660
|
Duration.Duration | undefined>(undefined);
|
|
1645
1661
|
}
|
|
1646
|
-
const retryAfter = parseRetryAfter(
|
|
1662
|
+
const retryAfter = parseRetryAfter(
|
|
1663
|
+
clock.currentTimeMillisUnsafe(),
|
|
1664
|
+
getHeader(response.headers, ...headerNames.retryAfter)
|
|
1665
|
+
)
|
|
1647
1666
|
if (retryAfter === undefined) {
|
|
1648
1667
|
return Effect.succeed</**
|
|
1649
1668
|
* Applies request rate limiting using the `RateLimiter` service.
|
|
@@ -1865,21 +1884,24 @@ const parseRateLimitWindow = (
|
|
|
1865
1884
|
}
|
|
1866
1885
|
|
|
1867
1886
|
const parseRetryAfter = (
|
|
1868
|
-
|
|
1887
|
+
nowMillis: number,
|
|
1869
1888
|
value: string | undefined
|
|
1870
1889
|
): Duration.Duration | undefined => {
|
|
1871
1890
|
if (value === undefined) {
|
|
1872
1891
|
return undefined
|
|
1873
1892
|
}
|
|
1874
|
-
const
|
|
1875
|
-
if (
|
|
1876
|
-
|
|
1893
|
+
const trimmed = value.trim()
|
|
1894
|
+
if (/^\d+(?:\.\d+)?$/.test(trimmed)) {
|
|
1895
|
+
const numeric = Number(trimmed)
|
|
1896
|
+
return Number.isFinite(numeric)
|
|
1897
|
+
? Duration.max(Duration.seconds(numeric), Duration.millis(1))
|
|
1898
|
+
: undefined
|
|
1877
1899
|
}
|
|
1878
|
-
const parsedDate = Date.parse(
|
|
1900
|
+
const parsedDate = Date.parse(trimmed)
|
|
1879
1901
|
if (Number.isNaN(parsedDate)) {
|
|
1880
1902
|
return undefined
|
|
1881
1903
|
}
|
|
1882
|
-
const millis = parsedDate -
|
|
1904
|
+
const millis = parsedDate - nowMillis
|
|
1883
1905
|
if (millis <= 0) {
|
|
1884
1906
|
return Duration.millis(1)
|
|
1885
1907
|
}
|
|
@@ -2417,6 +2439,28 @@ const isTransientHttpError = (error: unknown) =>
|
|
|
2417
2439
|
(error.reason._tag === "TransportError" ||
|
|
2418
2440
|
(error.reason._tag === "StatusCodeError" && isTransientResponse(error.reason.response)))
|
|
2419
2441
|
|
|
2442
|
+
const retryAfterOf = (input: unknown, nowMillis: number): Duration.Duration | undefined => {
|
|
2443
|
+
const response = Error.isHttpClientError(input)
|
|
2444
|
+
? input.response
|
|
2445
|
+
: Predicate.hasProperty(input, HttpClientResponse.TypeId)
|
|
2446
|
+
? input as HttpClientResponse.HttpClientResponse
|
|
2447
|
+
: undefined
|
|
2448
|
+
return parseRetryAfter(nowMillis, response?.headers["retry-after"])
|
|
2449
|
+
}
|
|
2450
|
+
|
|
2451
|
+
const withRetryAfter = <ES, R1>(
|
|
2452
|
+
schedule: Schedule.Schedule<unknown, any, ES, R1>
|
|
2453
|
+
): Schedule.Schedule<unknown, any, ES, R1> =>
|
|
2454
|
+
Schedule.fromStep(
|
|
2455
|
+
Effect.map(Schedule.toStep(schedule), (step) => (now: number, input: unknown) => {
|
|
2456
|
+
const retryAfter = retryAfterOf(input, now)
|
|
2457
|
+
if (retryAfter === undefined) {
|
|
2458
|
+
return step(now, input)
|
|
2459
|
+
}
|
|
2460
|
+
return Effect.map(step(now, input), ([output, delay]) => [output, Duration.max(delay, retryAfter)])
|
|
2461
|
+
})
|
|
2462
|
+
)
|
|
2463
|
+
|
|
2420
2464
|
const isTooManyRequestsHttpClientError = (
|
|
2421
2465
|
error: unknown
|
|
2422
2466
|
): error is Error.HttpClientError & { readonly reason: Error.StatusCodeError } =>
|
package/src/http/HttpEffect.ts
CHANGED
|
@@ -347,10 +347,11 @@ export const toWebHandlerWith = <Provided, R = never, ReqR = Exclude<R, Provided
|
|
|
347
347
|
const httpApp = toHandled(self, (request, response) => {
|
|
348
348
|
const withoutBody = request.method === "HEAD"
|
|
349
349
|
if (!Response.omitsBody(response, withoutBody)) {
|
|
350
|
+
// The transferred request scope closes when an abort interrupts the body, even if it is no longer read.
|
|
350
351
|
response = scopeTransferToStream(response)
|
|
351
352
|
}
|
|
352
353
|
;(request as any)[resolveSymbol](
|
|
353
|
-
Response.toWeb(response, { withoutBody, context })
|
|
354
|
+
Response.toWeb(response, { withoutBody, context, signal: (request.source as globalThis.Request).signal })
|
|
354
355
|
)
|
|
355
356
|
return Effect.void
|
|
356
357
|
}, middleware)
|
|
@@ -366,10 +367,15 @@ export const toWebHandlerWith = <Provided, R = never, ReqR = Exclude<R, Provided
|
|
|
366
367
|
reqContext = Context.add(reqContext, HttpServerRequest, httpServerRequest)
|
|
367
368
|
;(httpServerRequest as any)[resolveSymbol] = resolve
|
|
368
369
|
const fiber = Effect.runForkWith(reqContext)(httpApp as any)
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
370
|
+
const signal = request.signal
|
|
371
|
+
if (fiber.pollUnsafe() === undefined && signal) {
|
|
372
|
+
const onAbort = () => fiber.interruptUnsafe(undefined, ClientAbort.annotation)
|
|
373
|
+
if (signal.aborted) {
|
|
374
|
+
onAbort()
|
|
375
|
+
} else {
|
|
376
|
+
signal.addEventListener("abort", onAbort, { once: true })
|
|
377
|
+
fiber.addObserver(() => signal.removeEventListener("abort", onAbort))
|
|
378
|
+
}
|
|
373
379
|
}
|
|
374
380
|
})
|
|
375
381
|
}
|
|
@@ -268,42 +268,41 @@ export const tracer: <E, R>(
|
|
|
268
268
|
}
|
|
269
269
|
const redactedHeaderNames = fiber.getRef(Headers.CurrentRedactedNames)
|
|
270
270
|
const headerFilter = fiber.getRef(TracerHeaderFilter)
|
|
271
|
-
fiber
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
tracing.addUrlAttributes(span, url.value)
|
|
294
|
-
}
|
|
295
|
-
if (request.headers["user-agent"] !== undefined) {
|
|
296
|
-
span.attribute("user_agent.original", request.headers["user-agent"])
|
|
297
|
-
}
|
|
298
|
-
tracing.addHeaderAttributes(span, "request", request.headers, headerFilter, redactedHeaderNames)
|
|
299
|
-
if (Option.isSome(request.remoteAddress)) {
|
|
300
|
-
span.attribute("client.address", request.remoteAddress.value)
|
|
271
|
+
// Complete the span before the request fiber exits so shutdown can flush it.
|
|
272
|
+
let response: HttpServerResponse
|
|
273
|
+
let spanExit: Exit.Exit<HttpServerResponse, unknown>
|
|
274
|
+
if (Exit.isFailure(exit)) {
|
|
275
|
+
const [failureResponse, cause] = causeResponseStripped(exit.cause)
|
|
276
|
+
response = failureResponse
|
|
277
|
+
// OpenTelemetry only treats 5xx responses as server span errors; interrupts still fail the span.
|
|
278
|
+
spanExit = Option.isSome(cause) && (response.status >= 500 || Cause.hasInterrupts(cause.value))
|
|
279
|
+
? Exit.failCause(cause.value)
|
|
280
|
+
: responseSpanExit(request, response)
|
|
281
|
+
} else {
|
|
282
|
+
response = exit.value
|
|
283
|
+
spanExit = responseSpanExit(request, response)
|
|
284
|
+
}
|
|
285
|
+
if (span.sampled) {
|
|
286
|
+
span.attribute("http.request.method", request.method)
|
|
287
|
+
const url = Request.toURL(request)
|
|
288
|
+
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"
|
|
301
292
|
}
|
|
302
|
-
|
|
303
|
-
tracing.addHeaderAttributes(span, "response", response.headers, headerFilter, redactedHeaderNames)
|
|
293
|
+
tracing.addUrlAttributes(span, url.value)
|
|
304
294
|
}
|
|
305
|
-
|
|
306
|
-
|
|
295
|
+
if (request.headers["user-agent"] !== undefined) {
|
|
296
|
+
span.attribute("user_agent.original", request.headers["user-agent"])
|
|
297
|
+
}
|
|
298
|
+
tracing.addHeaderAttributes(span, "request", request.headers, headerFilter, redactedHeaderNames)
|
|
299
|
+
if (Option.isSome(request.remoteAddress)) {
|
|
300
|
+
span.attribute("client.address", request.remoteAddress.value)
|
|
301
|
+
}
|
|
302
|
+
span.attribute("http.response.status_code", response.status)
|
|
303
|
+
tracing.addHeaderAttributes(span, "response", response.headers, headerFilter, redactedHeaderNames)
|
|
304
|
+
}
|
|
305
|
+
span.end(endTime, spanExit)
|
|
307
306
|
return undefined
|
|
308
307
|
})
|
|
309
308
|
return httpApp
|
package/src/http/HttpRouter.ts
CHANGED
|
@@ -1410,6 +1410,41 @@ export const serve = <A, E, R, HE, HR = Request.Only<"Requires", R> | Request.On
|
|
|
1410
1410
|
* request arrives, in which case that request waits for the build to finish.
|
|
1411
1411
|
* If the build fails, every request rejects with the build error.
|
|
1412
1412
|
*
|
|
1413
|
+
* **Gotchas**
|
|
1414
|
+
*
|
|
1415
|
+
* On Cloudflare Workers, build the handler at module scope: fibers started
|
|
1416
|
+
* inside `fetch` capture that request's async context. Layer construction
|
|
1417
|
+
* must not require request-time I/O; bindings are available from
|
|
1418
|
+
* `cloudflare:workers` via its `env` export.
|
|
1419
|
+
* Prefer request-local work or a Durable Object's supported lifecycle APIs,
|
|
1420
|
+
* not indefinite background execution. For shared fibers, provide a sync
|
|
1421
|
+
* scheduler to both layers and requests as below; see `Scheduler.MixedScheduler`
|
|
1422
|
+
* for its limitations. Bound waits for shared work with request-local timeouts.
|
|
1423
|
+
*
|
|
1424
|
+
* **Example** (Opting in to the sync scheduler on Workers)
|
|
1425
|
+
*
|
|
1426
|
+
* ```ts
|
|
1427
|
+
* import { Effect, Layer, Scheduler } from "effect"
|
|
1428
|
+
* import { HttpRouter, HttpServer, HttpServerResponse } from "effect/http"
|
|
1429
|
+
*
|
|
1430
|
+
* const SyncScheduler = Layer.succeed(
|
|
1431
|
+
* Scheduler.Scheduler,
|
|
1432
|
+
* new Scheduler.MixedScheduler("sync")
|
|
1433
|
+
* )
|
|
1434
|
+
* const AppLive = HttpRouter.add(
|
|
1435
|
+
* "GET",
|
|
1436
|
+
* "/ping",
|
|
1437
|
+
* Effect.succeed(HttpServerResponse.text("pong"))
|
|
1438
|
+
* ).pipe(Layer.provide(HttpServer.layerServices))
|
|
1439
|
+
*
|
|
1440
|
+
* // Build once at module scope. The scheduler is also retained for requests.
|
|
1441
|
+
* const app = HttpRouter.toWebHandler(
|
|
1442
|
+
* AppLive.pipe(Layer.provideMerge(SyncScheduler))
|
|
1443
|
+
* )
|
|
1444
|
+
*
|
|
1445
|
+
* export default { fetch: (request: Request) => app.handler(request) }
|
|
1446
|
+
* ```
|
|
1447
|
+
*
|
|
1413
1448
|
* @stability unstable
|
|
1414
1449
|
* @category converting
|
|
1415
1450
|
* @since 4.0.0
|
|
@@ -1346,23 +1346,35 @@ export const setStatus: {
|
|
|
1346
1346
|
export const omitsBody = (response: HttpServerResponse, withoutBody = false): boolean =>
|
|
1347
1347
|
withoutBody || response.status === 204 || response.status === 205 || response.status === 304
|
|
1348
1348
|
|
|
1349
|
+
const mergeHeaders = (target: globalThis.Headers, headers: globalThis.Headers): globalThis.Headers => {
|
|
1350
|
+
for (const [key, value] of headers as any) {
|
|
1351
|
+
if (key === "set-cookie") {
|
|
1352
|
+
target.append(key, value)
|
|
1353
|
+
} else {
|
|
1354
|
+
target.set(key, value)
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return target
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1349
1360
|
/**
|
|
1350
1361
|
* Converts an `HttpServerResponse` to a Web `Response`.
|
|
1351
1362
|
*
|
|
1352
1363
|
* **Details**
|
|
1353
1364
|
*
|
|
1354
1365
|
* Cookies are appended as `Set-Cookie` headers. Stream bodies are converted using
|
|
1355
|
-
* the supplied context.
|
|
1366
|
+
* the supplied context, and are interrupted when `signal` aborts.
|
|
1356
1367
|
*
|
|
1357
1368
|
* Bodies are omitted for statuses 204, 205, and 304, or when `withoutBody` is true
|
|
1358
1369
|
* (for example, for HEAD responses). Omitted raw `ReadableStream` bodies are
|
|
1359
1370
|
* cancelled without awaiting completion, and cancellation errors are ignored.
|
|
1360
1371
|
*
|
|
1361
|
-
* A raw `Response`
|
|
1362
|
-
*
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1372
|
+
* A raw `Response` is returned as-is when there are no outer headers or cookies
|
|
1373
|
+
* to merge. Otherwise, they are merged into a copy without modifying the raw
|
|
1374
|
+
* response: outer headers replace native ones, and cookies are appended. When
|
|
1375
|
+
* its body is omitted, the bodyless `Response` keeps those merged
|
|
1376
|
+
* headers and uses the outer status for 204, 205 and 304, or the raw `Response`
|
|
1377
|
+
* status otherwise.
|
|
1366
1378
|
*
|
|
1367
1379
|
* @stability unstable
|
|
1368
1380
|
* @category converting
|
|
@@ -1373,6 +1385,7 @@ export const toWeb = (
|
|
|
1373
1385
|
options?: {
|
|
1374
1386
|
readonly withoutBody?: boolean | undefined
|
|
1375
1387
|
readonly context?: Context.Context<never> | undefined
|
|
1388
|
+
readonly signal?: AbortSignal | undefined
|
|
1376
1389
|
}
|
|
1377
1390
|
): Response => {
|
|
1378
1391
|
const headers = new globalThis.Headers(response.headers)
|
|
@@ -1387,15 +1400,13 @@ export const toWeb = (
|
|
|
1387
1400
|
const withoutBody = outerOmitsBody || options?.withoutBody === true
|
|
1388
1401
|
if (body._tag === "Raw" && body.body instanceof Response) {
|
|
1389
1402
|
const raw = body.body
|
|
1390
|
-
for (const [key, value] of headers as any) {
|
|
1391
|
-
if (key === "set-cookie") {
|
|
1392
|
-
raw.headers.append(key, value)
|
|
1393
|
-
} else {
|
|
1394
|
-
raw.headers.set(key, value)
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
1403
|
if (!withoutBody) {
|
|
1398
|
-
|
|
1404
|
+
if (headers.keys().next().done) {
|
|
1405
|
+
return raw
|
|
1406
|
+
}
|
|
1407
|
+
const copy = new Response(raw.body, raw)
|
|
1408
|
+
mergeHeaders(copy.headers, headers)
|
|
1409
|
+
return copy
|
|
1399
1410
|
}
|
|
1400
1411
|
// A bodyless outer status is what the caller chose to send; otherwise the
|
|
1401
1412
|
// body is only omitted for HEAD and the raw Response describes the resource.
|
|
@@ -1403,7 +1414,7 @@ export const toWeb = (
|
|
|
1403
1414
|
return new Response(undefined, {
|
|
1404
1415
|
status: source.status,
|
|
1405
1416
|
statusText: source.statusText as string,
|
|
1406
|
-
headers: raw.headers
|
|
1417
|
+
headers: mergeHeaders(new globalThis.Headers(raw.headers), headers)
|
|
1407
1418
|
})
|
|
1408
1419
|
}
|
|
1409
1420
|
if (withoutBody) {
|
|
@@ -1449,7 +1460,8 @@ export const toWeb = (
|
|
|
1449
1460
|
return new Response(
|
|
1450
1461
|
Stream.toReadableStreamWith(
|
|
1451
1462
|
body.stream,
|
|
1452
|
-
options?.context ?? Context.empty()
|
|
1463
|
+
options?.context ?? Context.empty(),
|
|
1464
|
+
{ signal: options?.signal }
|
|
1453
1465
|
),
|
|
1454
1466
|
{
|
|
1455
1467
|
status: response.status,
|
package/src/http/Multipart.ts
CHANGED
|
@@ -689,6 +689,22 @@ const defaultWriteFile = (path: string, file: File) =>
|
|
|
689
689
|
(fs) => Effect.mapError(Stream.run(file.content, fs.sink(path)), toMultipartError)
|
|
690
690
|
)
|
|
691
691
|
|
|
692
|
+
const maxPersistedFileNameBytes = 128
|
|
693
|
+
const fileNameEncoder = new TextEncoder()
|
|
694
|
+
|
|
695
|
+
// Keeps the trailing 128 UTF-8 bytes without splitting code points. Empty and dot names fall back to "file".
|
|
696
|
+
const persistedFileName = (baseName: string): string => {
|
|
697
|
+
const codePoints = Array.from(baseName)
|
|
698
|
+
let fileName = ""
|
|
699
|
+
let bytes = 0
|
|
700
|
+
for (let i = codePoints.length - 1; i >= 0; i--) {
|
|
701
|
+
bytes += fileNameEncoder.encode(codePoints[i]).length
|
|
702
|
+
if (bytes > maxPersistedFileNameBytes) break
|
|
703
|
+
fileName = codePoints[i] + fileName
|
|
704
|
+
}
|
|
705
|
+
return fileName === "" || fileName === "." || fileName === ".." ? "file" : fileName
|
|
706
|
+
}
|
|
707
|
+
|
|
692
708
|
/**
|
|
693
709
|
* Runs a channel of byte chunks and collects all output into a single
|
|
694
710
|
* `Uint8Array`.
|
|
@@ -748,7 +764,7 @@ export const toPersisted = (
|
|
|
748
764
|
return Effect.void
|
|
749
765
|
}
|
|
750
766
|
const file = part
|
|
751
|
-
const fileName = path_.basename(file.name)
|
|
767
|
+
const fileName = persistedFileName(path_.basename(file.name))
|
|
752
768
|
let path = path_.join(dir, fileName)
|
|
753
769
|
while (usedPaths.has(path)) {
|
|
754
770
|
path = path_.join(dir, `${fileIndex++}-${fileName}`)
|
|
@@ -60,6 +60,11 @@ export type ScalarConfig = {
|
|
|
60
60
|
layout?: "modern" | "classic"
|
|
61
61
|
/** URL to a request proxy for the API client */
|
|
62
62
|
proxyUrl?: string
|
|
63
|
+
/** Authentication preferences for the API client */
|
|
64
|
+
authentication?: {
|
|
65
|
+
/** OpenAPI security scheme names to select by default */
|
|
66
|
+
preferredSecurityScheme?: string | Array<string | Array<string>> | null
|
|
67
|
+
}
|
|
63
68
|
/** Browser JavaScript function expression used by Scalar for documents and test requests */
|
|
64
69
|
customFetch?: string
|
|
65
70
|
/** Whether to show the sidebar */
|
package/src/internal/dateTime.ts
CHANGED
|
@@ -934,10 +934,6 @@ export const subtractDuration: {
|
|
|
934
934
|
mapEpochMillis(self, (millis) => millis - Duration.toMillis(Duration.fromInputUnsafe(duration)))
|
|
935
935
|
)
|
|
936
936
|
|
|
937
|
-
const addMillis = (date: Date, amount: number): void => {
|
|
938
|
-
date.setTime(date.getTime() + amount)
|
|
939
|
-
}
|
|
940
|
-
|
|
941
937
|
/** @internal */
|
|
942
938
|
export const add: {
|
|
943
939
|
(
|
|
@@ -949,20 +945,26 @@ export const add: {
|
|
|
949
945
|
): A
|
|
950
946
|
} = dual(
|
|
951
947
|
2,
|
|
952
|
-
(self: DateTime.DateTime, parts: Partial<DateTime.DateTime.PartsForMath>): DateTime.DateTime =>
|
|
953
|
-
|
|
948
|
+
(self: DateTime.DateTime, parts: Partial<DateTime.DateTime.PartsForMath>): DateTime.DateTime => {
|
|
949
|
+
// time units move the instant, calendar units move the wall clock
|
|
950
|
+
const shifted = mutateUtc(self, (date) => {
|
|
954
951
|
if (parts.milliseconds) {
|
|
955
|
-
|
|
952
|
+
date.setTime(date.getTime() + parts.milliseconds)
|
|
956
953
|
}
|
|
957
954
|
if (parts.seconds) {
|
|
958
|
-
|
|
955
|
+
date.setTime(date.getTime() + parts.seconds * 1000)
|
|
959
956
|
}
|
|
960
957
|
if (parts.minutes) {
|
|
961
|
-
|
|
958
|
+
date.setTime(date.getTime() + parts.minutes * 60 * 1000)
|
|
962
959
|
}
|
|
963
960
|
if (parts.hours) {
|
|
964
|
-
|
|
961
|
+
date.setTime(date.getTime() + parts.hours * 60 * 60 * 1000)
|
|
965
962
|
}
|
|
963
|
+
})
|
|
964
|
+
if (!parts.days && !parts.weeks && !parts.months && !parts.years) {
|
|
965
|
+
return shifted
|
|
966
|
+
}
|
|
967
|
+
return mutate(shifted, (date) => {
|
|
966
968
|
if (parts.days) {
|
|
967
969
|
date.setUTCDate(date.getUTCDate() + parts.days)
|
|
968
970
|
}
|
|
@@ -989,6 +991,7 @@ export const add: {
|
|
|
989
991
|
}
|
|
990
992
|
}
|
|
991
993
|
})
|
|
994
|
+
}
|
|
992
995
|
)
|
|
993
996
|
|
|
994
997
|
/** @internal */
|
package/src/internal/effect.ts
CHANGED
|
@@ -1507,14 +1507,19 @@ const succeedPayload = function(this: { readonly payload: any }, _value: unknown
|
|
|
1507
1507
|
// V8 includes the property name of a stored continuation in its stack trace.
|
|
1508
1508
|
// Other engines need an explicit frame for the stack cleaner to cut at.
|
|
1509
1509
|
const continuationMarksStack = (() => {
|
|
1510
|
-
|
|
1510
|
+
const limit = getStackTraceLimit()
|
|
1511
|
+
if (limit === 0) return false
|
|
1511
1512
|
const marker = "~effect/Effect/stackProbe"
|
|
1512
1513
|
const probe = {
|
|
1513
1514
|
[marker]: function stackProbe() {
|
|
1514
|
-
return new Error()
|
|
1515
|
+
return new Error()
|
|
1515
1516
|
}
|
|
1516
1517
|
}
|
|
1517
|
-
|
|
1518
|
+
// Only the probe frame is needed to detect the continuation property name.
|
|
1519
|
+
setStackTraceLimit(1)
|
|
1520
|
+
const error = probe[marker]()
|
|
1521
|
+
setStackTraceLimit(limit)
|
|
1522
|
+
return error.stack?.includes("[as " + marker + "]") === true
|
|
1518
1523
|
})()
|
|
1519
1524
|
const mapCont = function(this: { readonly payload: any }, value: any, fiber: FiberImpl) {
|
|
1520
1525
|
const f = this.payload
|