effect 4.0.0-beta.24 → 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.
@@ -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>] ? Effect.Effect<Fiber.Fiber<_A, _E>, never, _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>,
@@ -8,7 +8,7 @@ import * as Duration from "../../Duration.ts"
8
8
  import * as Effect from "../../Effect.ts"
9
9
  import * as Exit from "../../Exit.ts"
10
10
  import * as Fiber from "../../Fiber.ts"
11
- import { constFalse, constTrue, dual, flow, identity } from "../../Function.ts"
11
+ import { constant, constFalse, constTrue, dual, flow, identity } from "../../Function.ts"
12
12
  import * as Inspectable from "../../Inspectable.ts"
13
13
  import * as Layer from "../../Layer.ts"
14
14
  import { type Pipeable, pipeArguments } from "../../Pipeable.ts"
@@ -1158,6 +1158,7 @@ export const withRateLimiter: {
1158
1158
  options: WithRateLimiter.Options
1159
1159
  ): HttpClient.With<E | RateLimiter.RateLimiterError, R> => {
1160
1160
  const initialState: RateLimiterState = {
1161
+ initial: true,
1161
1162
  limit: options.limit,
1162
1163
  window: Duration.max(Duration.fromInputUnsafe(options.window), Duration.millis(1))
1163
1164
  }
@@ -1166,10 +1167,11 @@ export const withRateLimiter: {
1166
1167
  const keyOption = options.key
1167
1168
  const resolveKey: (request: HttpClientRequest.HttpClientRequest) => string = typeof keyOption === "function"
1168
1169
  ? keyOption
1169
- : () => keyOption
1170
+ : constant(keyOption)
1170
1171
  const tokensOption = options.tokens
1171
- const resolveTokens: (request: HttpClientRequest.HttpClientRequest) => number | undefined =
1172
- typeof tokensOption === "function" ? tokensOption : () => tokensOption
1172
+ const resolveTokens: (request: HttpClientRequest.HttpClientRequest) => number = typeof tokensOption === "function"
1173
+ ? tokensOption
1174
+ : constant(tokensOption ?? 1)
1173
1175
 
1174
1176
  const getState = (key: string): RateLimiterState => {
1175
1177
  const current = states.get(key)
@@ -1182,7 +1184,7 @@ export const withRateLimiter: {
1182
1184
 
1183
1185
  const onResponse = options.disableResponseInspection
1184
1186
  ? undefined
1185
- : (clock: Clock, key: string, headers: Headers.Headers, tokens: number | undefined) => {
1187
+ : (clock: Clock, key: string, headers: Headers.Headers, tokens: number) => {
1186
1188
  const current = getState(key)
1187
1189
  const next = parseRateLimiterState(current, clock, headers, tokens)
1188
1190
  if (next.limit !== current.limit || !Duration.equals(next.window, current.window)) {
@@ -1198,8 +1200,15 @@ export const withRateLimiter: {
1198
1200
  const fiber = Fiber.getCurrent()!
1199
1201
  const clock = fiber.getRef(Clock)
1200
1202
  const key = resolveKey(request)
1201
- const tokens = resolveTokens(request)
1203
+ const tokens = Math.max(resolveTokens(request), 1)
1202
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
+ }
1203
1212
  return Effect.flatMap(
1204
1213
  options.limiter.consume({
1205
1214
  algorithm: options.algorithm,
@@ -1213,12 +1222,13 @@ export const withRateLimiter: {
1213
1222
  const run = Effect.matchEffect(effect, {
1214
1223
  onSuccess(response) {
1215
1224
  onResponse?.(clock, key, response.headers, tokens)
1216
- return response.status === 429 ? loop(effect, request) : Effect.succeed(response)
1225
+ if (response.status !== 429) return Effect.succeed(response)
1226
+ return retry(response)
1217
1227
  },
1218
1228
  onFailure(error) {
1219
1229
  if (isTooManyRequestsHttpClientError(error)) {
1220
1230
  onResponse?.(clock, key, error.reason.response.headers, tokens)
1221
- return loop(effect, request)
1231
+ return retry(error.reason.response)
1222
1232
  }
1223
1233
  return Effect.fail(error)
1224
1234
  }
@@ -1232,23 +1242,28 @@ export const withRateLimiter: {
1232
1242
  interface RateLimiterState {
1233
1243
  readonly limit: number
1234
1244
  readonly window: Duration.Duration
1245
+ readonly initial: boolean
1235
1246
  }
1236
1247
 
1237
1248
  const parseRateLimiterState = (
1238
1249
  state: RateLimiterState,
1239
1250
  clock: Clock,
1240
1251
  headers: Headers.Headers,
1241
- tokens: number | undefined
1252
+ tokens: number
1242
1253
  ): RateLimiterState => {
1243
- const limit = parseRateLimitLimit(headers, tokens) ?? state.limit
1254
+ const limit = parseRateLimitLimit(state, headers, tokens) ?? state.limit
1244
1255
  const window = parseRateLimitWindow(clock, headers) ?? state.window
1245
1256
  if (limit === state.limit && Duration.equals(window, state.window)) {
1246
1257
  return state
1247
1258
  }
1248
- return { limit, window }
1259
+ return { limit, window, initial: false }
1249
1260
  }
1250
1261
 
1251
- const parseRateLimitLimit = (headers: Headers.Headers, tokens: number | undefined): number | undefined => {
1262
+ const parseRateLimitLimit = (
1263
+ state: RateLimiterState,
1264
+ headers: Headers.Headers,
1265
+ tokens: number
1266
+ ): number | undefined => {
1252
1267
  const raw = getHeader(headers, "ratelimit-limit", "x-ratelimit-limit")
1253
1268
  const value = parseNumberHeader(raw)
1254
1269
  if (value !== undefined && value > 0) {
@@ -1258,7 +1273,7 @@ const parseRateLimitLimit = (headers: Headers.Headers, tokens: number | undefine
1258
1273
  if (remaining === undefined) {
1259
1274
  return undefined
1260
1275
  }
1261
- return Math.max(remaining + (tokens !== undefined && tokens > 0 ? tokens : 1), 1)
1276
+ return state.initial ? remaining + tokens : Math.max(remaining + tokens, state.limit)
1262
1277
  }
1263
1278
 
1264
1279
  const parseRateLimitRemaining = (headers: Headers.Headers): number | undefined => {
@@ -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: (entry) => entry.services.mapUnsafe.get(SqlClient.TransactionConnection.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* Effect.fail(new ResultLengthMismatch({ expected: inputs.length, actual: results.length }))
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: (entry) => entry.services.mapUnsafe.get(SqlClient.TransactionConnection.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: (entry) => entry.services.mapUnsafe.get(SqlClient.TransactionConnection.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: (entry) => entry.services.mapUnsafe.get(SqlClient.TransactionConnection.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
+ }