effect 4.0.0-beta.23 → 4.0.0-beta.25
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/Effect.d.ts +1 -1
- package/dist/Effect.d.ts.map +1 -1
- package/dist/Schema.d.ts +21 -0
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +61 -17
- package/dist/Schema.js.map +1 -1
- package/dist/SchemaAST.d.ts.map +1 -1
- package/dist/SchemaAST.js +26 -14
- package/dist/SchemaAST.js.map +1 -1
- package/dist/SchemaRepresentation.d.ts.map +1 -1
- package/dist/SchemaRepresentation.js +2 -0
- package/dist/SchemaRepresentation.js.map +1 -1
- package/dist/ServiceMap.d.ts +1 -0
- package/dist/ServiceMap.d.ts.map +1 -1
- package/dist/ServiceMap.js.map +1 -1
- package/dist/internal/effect.js.map +1 -1
- package/dist/internal/schema/representation.js +1 -1
- package/dist/internal/schema/representation.js.map +1 -1
- package/dist/testing/TestClock.d.ts +2 -2
- package/dist/unstable/http/HttpClient.d.ts +80 -2
- package/dist/unstable/http/HttpClient.d.ts.map +1 -1
- package/dist/unstable/http/HttpClient.js +179 -1
- package/dist/unstable/http/HttpClient.js.map +1 -1
- package/dist/unstable/httpapi/HttpApi.d.ts +1 -1
- package/dist/unstable/httpapi/HttpApi.d.ts.map +1 -1
- package/dist/unstable/rpc/RpcServer.d.ts +2 -2
- package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
- package/dist/unstable/sql/SqlResolver.d.ts.map +1 -1
- package/dist/unstable/sql/SqlResolver.js +15 -6
- package/dist/unstable/sql/SqlResolver.js.map +1 -1
- package/package.json +1 -1
- package/src/Effect.ts +1 -1
- package/src/Schema.ts +98 -18
- package/src/SchemaAST.ts +24 -19
- package/src/SchemaRepresentation.ts +2 -0
- package/src/ServiceMap.ts +1 -0
- package/src/internal/effect.ts +2 -1
- package/src/internal/schema/representation.ts +1 -0
- package/src/unstable/http/HttpClient.ts +306 -3
- package/src/unstable/httpapi/HttpApi.ts +1 -1
- package/src/unstable/sql/SqlResolver.ts +15 -5
package/src/internal/effect.ts
CHANGED
|
@@ -4950,7 +4950,8 @@ export const forkScoped: {
|
|
|
4950
4950
|
readonly startImmediately?: boolean | undefined
|
|
4951
4951
|
readonly uninterruptible?: boolean | "inherit" | undefined
|
|
4952
4952
|
} | undefined
|
|
4953
|
-
): [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ?
|
|
4953
|
+
): [Arg] extends [Effect.Effect<infer _A, infer _E, infer _R>] ?
|
|
4954
|
+
Effect.Effect<Fiber.Fiber<_A, _E>, never, _R | Scope.Scope>
|
|
4954
4955
|
: <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<Fiber.Fiber<A, E>, never, R | Scope.Scope>
|
|
4955
4956
|
} = dual((args) => isEffect(args[0]), <A, E, R>(
|
|
4956
4957
|
self: Effect.Effect<A, E, R>,
|
|
@@ -268,6 +268,7 @@ export function fromASTs(asts: readonly [AST.AST, ...Array<AST.AST>]): SchemaRep
|
|
|
268
268
|
export const fromASTBlacklist: Set<string> = new Set([
|
|
269
269
|
// `expected` is preserved because is useful to generate descriptions in JSON Schemas
|
|
270
270
|
"~structural",
|
|
271
|
+
"~sentinels",
|
|
271
272
|
"meta",
|
|
272
273
|
"toArbitrary",
|
|
273
274
|
"toArbitraryConstraint",
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { NonEmptyReadonlyArray } from "../../Array.ts"
|
|
5
5
|
import * as Cause from "../../Cause.ts"
|
|
6
|
+
import { Clock } from "../../Clock.ts"
|
|
7
|
+
import * as Duration from "../../Duration.ts"
|
|
6
8
|
import * as Effect from "../../Effect.ts"
|
|
7
9
|
import * as Exit from "../../Exit.ts"
|
|
8
|
-
import
|
|
9
|
-
import { constFalse, constTrue, dual, flow, identity } from "../../Function.ts"
|
|
10
|
+
import * as Fiber from "../../Fiber.ts"
|
|
11
|
+
import { constant, constFalse, constTrue, dual, flow, identity } from "../../Function.ts"
|
|
10
12
|
import * as Inspectable from "../../Inspectable.ts"
|
|
11
13
|
import * as Layer from "../../Layer.ts"
|
|
12
14
|
import { type Pipeable, pipeArguments } from "../../Pipeable.ts"
|
|
@@ -19,6 +21,7 @@ import * as ServiceMap from "../../ServiceMap.ts"
|
|
|
19
21
|
import * as Stream from "../../Stream.ts"
|
|
20
22
|
import * as Tracer from "../../Tracer.ts"
|
|
21
23
|
import type { EqualsWith, ExcludeTag, ExtractTag, NoExcessProperties, NoInfer, Tags } from "../../Types.ts"
|
|
24
|
+
import type * as RateLimiter from "../persistence/RateLimiter.ts"
|
|
22
25
|
import * as Cookies from "./Cookies.ts"
|
|
23
26
|
import * as Headers from "./Headers.ts"
|
|
24
27
|
import * as Error from "./HttpClientError.ts"
|
|
@@ -630,7 +633,7 @@ export const make = (
|
|
|
630
633
|
request: HttpClientRequest.HttpClientRequest,
|
|
631
634
|
url: URL,
|
|
632
635
|
signal: AbortSignal,
|
|
633
|
-
fiber: Fiber<HttpClientResponse.HttpClientResponse, Error.HttpClientError>
|
|
636
|
+
fiber: Fiber.Fiber<HttpClientResponse.HttpClientResponse, Error.HttpClientError>
|
|
634
637
|
) => Effect.Effect<HttpClientResponse.HttpClientResponse, Error.HttpClientError>
|
|
635
638
|
): HttpClient =>
|
|
636
639
|
makeWith((effect) =>
|
|
@@ -1072,6 +1075,301 @@ export const retryTransient: {
|
|
|
1072
1075
|
}
|
|
1073
1076
|
)
|
|
1074
1077
|
|
|
1078
|
+
/**
|
|
1079
|
+
* @since 4.0.0
|
|
1080
|
+
* @category rate limiting
|
|
1081
|
+
*/
|
|
1082
|
+
export declare namespace WithRateLimiter {
|
|
1083
|
+
/**
|
|
1084
|
+
* @since 4.0.0
|
|
1085
|
+
* @category rate limiting
|
|
1086
|
+
*/
|
|
1087
|
+
export interface Options {
|
|
1088
|
+
/**
|
|
1089
|
+
* The `RateLimiter` service to use for rate limiting.
|
|
1090
|
+
*/
|
|
1091
|
+
readonly limiter: RateLimiter.RateLimiter
|
|
1092
|
+
/**
|
|
1093
|
+
* The initial rate limit window duration.
|
|
1094
|
+
*/
|
|
1095
|
+
readonly window: Duration.Input
|
|
1096
|
+
/**
|
|
1097
|
+
* The initial maximum number of allowed requests in the window.
|
|
1098
|
+
*/
|
|
1099
|
+
readonly limit: number
|
|
1100
|
+
/**
|
|
1101
|
+
* The key to identify the rate limit. Requests with the same key will share
|
|
1102
|
+
* the same rate limit. This can be used to implement per-user or
|
|
1103
|
+
* per-endpoint rate limits.
|
|
1104
|
+
*/
|
|
1105
|
+
readonly key: string | ((request: HttpClientRequest.HttpClientRequest) => string)
|
|
1106
|
+
/**
|
|
1107
|
+
* Defaults to `"fixed-window"`.
|
|
1108
|
+
*/
|
|
1109
|
+
readonly algorithm?: "fixed-window" | "token-bucket" | undefined
|
|
1110
|
+
/**
|
|
1111
|
+
* Defaults to `1`.
|
|
1112
|
+
*/
|
|
1113
|
+
readonly tokens?: number | ((request: HttpClientRequest.HttpClientRequest) => number) | undefined
|
|
1114
|
+
/**
|
|
1115
|
+
* Disable automatic limits updates from response headers.
|
|
1116
|
+
*/
|
|
1117
|
+
readonly disableResponseInspection?: boolean | undefined
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Applies request rate limiting using the `RateLimiter` service.
|
|
1123
|
+
*
|
|
1124
|
+
* It can update limits by inspecting common rate limit response headers and
|
|
1125
|
+
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1126
|
+
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1127
|
+
*
|
|
1128
|
+
* @since 4.0.0
|
|
1129
|
+
* @category rate limiting
|
|
1130
|
+
*/
|
|
1131
|
+
export const withRateLimiter: {
|
|
1132
|
+
/**
|
|
1133
|
+
* Applies request rate limiting using the `RateLimiter` service.
|
|
1134
|
+
*
|
|
1135
|
+
* It can update limits by inspecting common rate limit response headers and
|
|
1136
|
+
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1137
|
+
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1138
|
+
*
|
|
1139
|
+
* @since 4.0.0
|
|
1140
|
+
* @category rate limiting
|
|
1141
|
+
*/
|
|
1142
|
+
(options: WithRateLimiter.Options): <E, R>(
|
|
1143
|
+
self: HttpClient.With<E, R>
|
|
1144
|
+
) => HttpClient.With<E | RateLimiter.RateLimiterError, R>
|
|
1145
|
+
/**
|
|
1146
|
+
* Applies request rate limiting using the `RateLimiter` service.
|
|
1147
|
+
*
|
|
1148
|
+
* It can update limits by inspecting common rate limit response headers and
|
|
1149
|
+
* automatically retries HTTP `429` responses (or `HttpClientError` values
|
|
1150
|
+
* wrapping a `429` response) by forcing the retry back through the limiter.
|
|
1151
|
+
*
|
|
1152
|
+
* @since 4.0.0
|
|
1153
|
+
* @category rate limiting
|
|
1154
|
+
*/
|
|
1155
|
+
<E, R>(self: HttpClient.With<E, R>, options: WithRateLimiter.Options): HttpClient.With<E | RateLimiter.RateLimiterError, R>
|
|
1156
|
+
} = dual(2, <E, R>(
|
|
1157
|
+
self: HttpClient.With<E, R>,
|
|
1158
|
+
options: WithRateLimiter.Options
|
|
1159
|
+
): HttpClient.With<E | RateLimiter.RateLimiterError, R> => {
|
|
1160
|
+
const initialState: RateLimiterState = {
|
|
1161
|
+
initial: true,
|
|
1162
|
+
limit: options.limit,
|
|
1163
|
+
window: Duration.max(Duration.fromInputUnsafe(options.window), Duration.millis(1))
|
|
1164
|
+
}
|
|
1165
|
+
const states = new Map<string, RateLimiterState>()
|
|
1166
|
+
|
|
1167
|
+
const keyOption = options.key
|
|
1168
|
+
const resolveKey: (request: HttpClientRequest.HttpClientRequest) => string = typeof keyOption === "function"
|
|
1169
|
+
? keyOption
|
|
1170
|
+
: constant(keyOption)
|
|
1171
|
+
const tokensOption = options.tokens
|
|
1172
|
+
const resolveTokens: (request: HttpClientRequest.HttpClientRequest) => number = typeof tokensOption === "function"
|
|
1173
|
+
? tokensOption
|
|
1174
|
+
: constant(tokensOption ?? 1)
|
|
1175
|
+
|
|
1176
|
+
const getState = (key: string): RateLimiterState => {
|
|
1177
|
+
const current = states.get(key)
|
|
1178
|
+
if (current !== undefined) {
|
|
1179
|
+
return current
|
|
1180
|
+
}
|
|
1181
|
+
states.set(key, initialState)
|
|
1182
|
+
return initialState
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const onResponse = options.disableResponseInspection
|
|
1186
|
+
? undefined
|
|
1187
|
+
: (clock: Clock, key: string, headers: Headers.Headers, tokens: number) => {
|
|
1188
|
+
const current = getState(key)
|
|
1189
|
+
const next = parseRateLimiterState(current, clock, headers, tokens)
|
|
1190
|
+
if (next.limit !== current.limit || !Duration.equals(next.window, current.window)) {
|
|
1191
|
+
states.set(key, next)
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
return transform(self, function loop(effect, request): Effect.Effect<
|
|
1196
|
+
HttpClientResponse.HttpClientResponse,
|
|
1197
|
+
E | RateLimiter.RateLimiterError,
|
|
1198
|
+
R
|
|
1199
|
+
> {
|
|
1200
|
+
const fiber = Fiber.getCurrent()!
|
|
1201
|
+
const clock = fiber.getRef(Clock)
|
|
1202
|
+
const key = resolveKey(request)
|
|
1203
|
+
const tokens = Math.max(resolveTokens(request), 1)
|
|
1204
|
+
const current = getState(key)
|
|
1205
|
+
function retry(response: HttpClientResponse.HttpClientResponse) {
|
|
1206
|
+
if (options.disableResponseInspection) return loop(effect, request)
|
|
1207
|
+
const retryAfter = parseRetryAfter(clock, getHeader(response.headers, "retry-after"))
|
|
1208
|
+
return retryAfter
|
|
1209
|
+
? Effect.flatMap(Effect.sleep(retryAfter), () => loop(effect, request))
|
|
1210
|
+
: loop(effect, request)
|
|
1211
|
+
}
|
|
1212
|
+
return Effect.flatMap(
|
|
1213
|
+
options.limiter.consume({
|
|
1214
|
+
algorithm: options.algorithm,
|
|
1215
|
+
onExceeded: "delay",
|
|
1216
|
+
key,
|
|
1217
|
+
limit: current.limit,
|
|
1218
|
+
window: current.window,
|
|
1219
|
+
tokens
|
|
1220
|
+
}),
|
|
1221
|
+
({ delay }) => {
|
|
1222
|
+
const run = Effect.matchEffect(effect, {
|
|
1223
|
+
onSuccess(response) {
|
|
1224
|
+
onResponse?.(clock, key, response.headers, tokens)
|
|
1225
|
+
if (response.status !== 429) return Effect.succeed(response)
|
|
1226
|
+
return retry(response)
|
|
1227
|
+
},
|
|
1228
|
+
onFailure(error) {
|
|
1229
|
+
if (isTooManyRequestsHttpClientError(error)) {
|
|
1230
|
+
onResponse?.(clock, key, error.reason.response.headers, tokens)
|
|
1231
|
+
return retry(error.reason.response)
|
|
1232
|
+
}
|
|
1233
|
+
return Effect.fail(error)
|
|
1234
|
+
}
|
|
1235
|
+
})
|
|
1236
|
+
return Duration.isZero(delay) ? run : Effect.delay(run, delay)
|
|
1237
|
+
}
|
|
1238
|
+
)
|
|
1239
|
+
})
|
|
1240
|
+
})
|
|
1241
|
+
|
|
1242
|
+
interface RateLimiterState {
|
|
1243
|
+
readonly limit: number
|
|
1244
|
+
readonly window: Duration.Duration
|
|
1245
|
+
readonly initial: boolean
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
const parseRateLimiterState = (
|
|
1249
|
+
state: RateLimiterState,
|
|
1250
|
+
clock: Clock,
|
|
1251
|
+
headers: Headers.Headers,
|
|
1252
|
+
tokens: number
|
|
1253
|
+
): RateLimiterState => {
|
|
1254
|
+
const limit = parseRateLimitLimit(state, headers, tokens) ?? state.limit
|
|
1255
|
+
const window = parseRateLimitWindow(clock, headers) ?? state.window
|
|
1256
|
+
if (limit === state.limit && Duration.equals(window, state.window)) {
|
|
1257
|
+
return state
|
|
1258
|
+
}
|
|
1259
|
+
return { limit, window, initial: false }
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
const parseRateLimitLimit = (
|
|
1263
|
+
state: RateLimiterState,
|
|
1264
|
+
headers: Headers.Headers,
|
|
1265
|
+
tokens: number
|
|
1266
|
+
): number | undefined => {
|
|
1267
|
+
const raw = getHeader(headers, "ratelimit-limit", "x-ratelimit-limit")
|
|
1268
|
+
const value = parseNumberHeader(raw)
|
|
1269
|
+
if (value !== undefined && value > 0) {
|
|
1270
|
+
return value
|
|
1271
|
+
}
|
|
1272
|
+
const remaining = parseRateLimitRemaining(headers)
|
|
1273
|
+
if (remaining === undefined) {
|
|
1274
|
+
return undefined
|
|
1275
|
+
}
|
|
1276
|
+
return state.initial ? remaining + tokens : Math.max(remaining + tokens, state.limit)
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
const parseRateLimitRemaining = (headers: Headers.Headers): number | undefined => {
|
|
1280
|
+
const raw = getHeader(headers, "ratelimit-remaining", "x-ratelimit-remaining")
|
|
1281
|
+
const value = parseNumberHeader(raw)
|
|
1282
|
+
return value !== undefined && value >= 0 ? value : undefined
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
const parseRateLimitWindow = (
|
|
1286
|
+
clock: Clock,
|
|
1287
|
+
headers: Headers.Headers
|
|
1288
|
+
): Duration.Duration | undefined => {
|
|
1289
|
+
const retryAfter = parseRetryAfter(
|
|
1290
|
+
clock,
|
|
1291
|
+
getHeader(headers, "retry-after")
|
|
1292
|
+
)
|
|
1293
|
+
if (retryAfter !== undefined) {
|
|
1294
|
+
return retryAfter
|
|
1295
|
+
}
|
|
1296
|
+
const resetAfter = parseResetAfter(getHeader(headers, "ratelimit-reset-after", "x-ratelimit-reset-after"))
|
|
1297
|
+
if (resetAfter !== undefined) {
|
|
1298
|
+
return resetAfter
|
|
1299
|
+
}
|
|
1300
|
+
return parseResetHeader(clock, getHeader(headers, "ratelimit-reset", "x-ratelimit-reset"))
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const parseRetryAfter = (
|
|
1304
|
+
clock: Clock,
|
|
1305
|
+
value: string | undefined
|
|
1306
|
+
): Duration.Duration | undefined => {
|
|
1307
|
+
if (value === undefined) {
|
|
1308
|
+
return undefined
|
|
1309
|
+
}
|
|
1310
|
+
const numeric = parseNumberHeader(value)
|
|
1311
|
+
if (numeric !== undefined) {
|
|
1312
|
+
return Duration.max(Duration.seconds(numeric), Duration.millis(1))
|
|
1313
|
+
}
|
|
1314
|
+
const parsedDate = Date.parse(value)
|
|
1315
|
+
if (Number.isNaN(parsedDate)) {
|
|
1316
|
+
return undefined
|
|
1317
|
+
}
|
|
1318
|
+
const millis = parsedDate - clock.currentTimeMillisUnsafe()
|
|
1319
|
+
if (millis <= 0) {
|
|
1320
|
+
return Duration.millis(1)
|
|
1321
|
+
}
|
|
1322
|
+
return Duration.millis(millis)
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const parseResetAfter = (value: string | undefined): Duration.Duration | undefined => {
|
|
1326
|
+
const numeric = parseNumberHeader(value)
|
|
1327
|
+
if (numeric === undefined || numeric <= 0) {
|
|
1328
|
+
return undefined
|
|
1329
|
+
}
|
|
1330
|
+
return Duration.max(Duration.seconds(numeric), Duration.millis(1))
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
const parseResetHeader = (
|
|
1334
|
+
clock: Clock,
|
|
1335
|
+
value: string | undefined
|
|
1336
|
+
): Duration.Duration | undefined => {
|
|
1337
|
+
const numeric = parseNumberHeader(value)
|
|
1338
|
+
if (numeric === undefined || numeric <= 0) {
|
|
1339
|
+
return undefined
|
|
1340
|
+
}
|
|
1341
|
+
const nowMillis = clock.currentTimeMillisUnsafe()
|
|
1342
|
+
if (numeric > 1_000_000_000_000) {
|
|
1343
|
+
return Duration.millis(Math.max(numeric - nowMillis, 1))
|
|
1344
|
+
}
|
|
1345
|
+
if (numeric > 1_000_000_000) {
|
|
1346
|
+
return Duration.millis(Math.max((numeric * 1_000) - nowMillis, 1))
|
|
1347
|
+
}
|
|
1348
|
+
return Duration.max(Duration.seconds(numeric), Duration.millis(1))
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const parseNumberHeader = (value: string | undefined): number | undefined => {
|
|
1352
|
+
if (value === undefined) {
|
|
1353
|
+
return undefined
|
|
1354
|
+
}
|
|
1355
|
+
const match = /-?\d+(?:\.\d+)?/.exec(value)
|
|
1356
|
+
if (match === null) {
|
|
1357
|
+
return undefined
|
|
1358
|
+
}
|
|
1359
|
+
const parsed = Number(match[0])
|
|
1360
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const getHeader = (headers: Headers.Headers, ...keys: Array<string>): string | undefined => {
|
|
1364
|
+
for (let i = 0; i < keys.length; i++) {
|
|
1365
|
+
const value = headers[keys[i]]
|
|
1366
|
+
if (value !== undefined) {
|
|
1367
|
+
return value
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return undefined
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1075
1373
|
/**
|
|
1076
1374
|
* Performs an additional effect after a successful request.
|
|
1077
1375
|
*
|
|
@@ -1462,6 +1760,11 @@ const isTransientHttpError = (error: unknown) =>
|
|
|
1462
1760
|
(error.reason._tag === "TransportError" ||
|
|
1463
1761
|
(error.reason._tag === "StatusCodeError" && isTransientResponse(error.reason.response)))
|
|
1464
1762
|
|
|
1763
|
+
const isTooManyRequestsHttpClientError = (
|
|
1764
|
+
error: unknown
|
|
1765
|
+
): error is Error.HttpClientError & { readonly reason: Error.StatusCodeError } =>
|
|
1766
|
+
Error.isHttpClientError(error) && error.reason._tag === "StatusCodeError" && error.reason.response.status === 429
|
|
1767
|
+
|
|
1465
1768
|
const isTransientResponse = (response: HttpClientResponse.HttpClientResponse) =>
|
|
1466
1769
|
response.status === 408 ||
|
|
1467
1770
|
response.status === 429 ||
|
|
@@ -57,7 +57,7 @@ export interface HttpApi<
|
|
|
57
57
|
/**
|
|
58
58
|
* Prefix all endpoints in the `HttpApi`.
|
|
59
59
|
*/
|
|
60
|
-
prefix<const Prefix extends PathInput>(prefix: Prefix): HttpApi<Id, Groups
|
|
60
|
+
prefix<const Prefix extends PathInput>(prefix: Prefix): HttpApi<Id, HttpApiGroup.AddPrefix<Groups, Prefix>>
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Add a middleware to a `HttpApi`. It will be applied to all endpoints in the
|
|
@@ -104,14 +104,14 @@ export const ordered = <Req extends Schema.Top, Res extends Schema.Top, _, E, R>
|
|
|
104
104
|
>,
|
|
105
105
|
SqlClient.TransactionConnection["Service"] | undefined
|
|
106
106
|
>({
|
|
107
|
-
key:
|
|
107
|
+
key: transactionKey,
|
|
108
108
|
resolver: Effect.fnUntraced(function*(entries) {
|
|
109
109
|
const inputs = yield* partitionRequests(entries, options.Request)
|
|
110
110
|
const results = yield* options.execute(inputs as any).pipe(
|
|
111
111
|
Effect.provideServices(entries[0].services)
|
|
112
112
|
)
|
|
113
113
|
if (results.length !== inputs.length) {
|
|
114
|
-
return yield*
|
|
114
|
+
return yield* new ResultLengthMismatch({ expected: inputs.length, actual: results.length })
|
|
115
115
|
}
|
|
116
116
|
const decodedResults = yield* decodeArray(results).pipe(
|
|
117
117
|
Effect.provideServices(entries[0].services)
|
|
@@ -160,7 +160,7 @@ export const grouped = <Req extends Schema.Top, Res extends Schema.Top, K, Row,
|
|
|
160
160
|
>,
|
|
161
161
|
SqlClient.TransactionConnection["Service"] | undefined
|
|
162
162
|
>({
|
|
163
|
-
key:
|
|
163
|
+
key: transactionKey,
|
|
164
164
|
resolver: Effect.fnUntraced(function*(entries) {
|
|
165
165
|
const inputs = yield* partitionRequests(entries, options.Request)
|
|
166
166
|
const resultMap = MutableHashMap.empty<K, Arr.NonEmptyArray<Res["Type"]>>()
|
|
@@ -226,7 +226,11 @@ export const findById = <Id extends Schema.Top, Res extends Schema.Top, Row, E,
|
|
|
226
226
|
>,
|
|
227
227
|
SqlClient.TransactionConnection["Service"] | undefined
|
|
228
228
|
>({
|
|
229
|
-
key
|
|
229
|
+
key(entry) {
|
|
230
|
+
const conn = entry.services.mapUnsafe.get(SqlClient.TransactionConnection.key)
|
|
231
|
+
if (!conn) return undefined
|
|
232
|
+
return Equal.byReferenceUnsafe(conn)
|
|
233
|
+
},
|
|
230
234
|
resolver: Effect.fnUntraced(function*(entries) {
|
|
231
235
|
const [inputs, idMap] = yield* partitionRequestsById(entries, options.Id)
|
|
232
236
|
const results = yield* options.execute(inputs as any).pipe(
|
|
@@ -279,7 +283,7 @@ const void_ = <Req extends Schema.Top, _, E, R>(
|
|
|
279
283
|
>,
|
|
280
284
|
SqlClient.TransactionConnection["Service"] | undefined
|
|
281
285
|
>({
|
|
282
|
-
key:
|
|
286
|
+
key: transactionKey,
|
|
283
287
|
resolver: Effect.fnUntraced(function*(entries) {
|
|
284
288
|
const inputs = yield* partitionRequests(entries, options.Request)
|
|
285
289
|
yield* options.execute(inputs as any).pipe(
|
|
@@ -354,3 +358,9 @@ const partitionRequestsById = function*<In, A, E, R, InE>(
|
|
|
354
358
|
|
|
355
359
|
return [inputs, byIdMap] as const
|
|
356
360
|
}
|
|
361
|
+
|
|
362
|
+
function transactionKey<A>(entry: Request.Entry<A>): SqlClient.TransactionConnection["Service"] | undefined {
|
|
363
|
+
const conn = entry.services.mapUnsafe.get(SqlClient.TransactionConnection.key)
|
|
364
|
+
if (!conn) return undefined
|
|
365
|
+
return Equal.byReferenceUnsafe(conn)
|
|
366
|
+
}
|