effect 4.0.0-beta.104 → 4.0.0-beta.105

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 (191) hide show
  1. package/AGENTS.md +381 -0
  2. package/CLAUDE.md +381 -0
  3. package/ai-docs/README.md +44 -0
  4. package/ai-docs/package.json +36 -0
  5. package/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +30 -0
  6. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +39 -0
  7. package/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +74 -0
  8. package/ai-docs/src/01_effect/01_basics/index.md +5 -0
  9. package/ai-docs/src/01_effect/02_schema/10_schema-basics.ts +43 -0
  10. package/ai-docs/src/01_effect/02_schema/index.md +7 -0
  11. package/ai-docs/src/01_effect/03_services/01_service.ts +45 -0
  12. package/ai-docs/src/01_effect/03_services/10_reference.ts +10 -0
  13. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +70 -0
  14. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +66 -0
  15. package/ai-docs/src/01_effect/03_services/index.md +5 -0
  16. package/ai-docs/src/01_effect/04_errors/01_error-handling.ts +30 -0
  17. package/ai-docs/src/01_effect/04_errors/10_catch-tags.ts +24 -0
  18. package/ai-docs/src/01_effect/04_errors/20_reason-errors.ts +64 -0
  19. package/ai-docs/src/01_effect/04_errors/index.md +1 -0
  20. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +105 -0
  21. package/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts +31 -0
  22. package/ai-docs/src/01_effect/05_resources/30_layer-map.ts +86 -0
  23. package/ai-docs/src/01_effect/05_resources/index.md +3 -0
  24. package/ai-docs/src/01_effect/06_running/10_run-main.ts +30 -0
  25. package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +27 -0
  26. package/ai-docs/src/01_effect/06_running/index.md +1 -0
  27. package/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts +56 -0
  28. package/ai-docs/src/01_effect/07_pubsub/index.md +3 -0
  29. package/ai-docs/src/03_stream/10_creating-streams.ts +103 -0
  30. package/ai-docs/src/03_stream/20_consuming-streams.ts +137 -0
  31. package/ai-docs/src/03_stream/30_encoding.ts +165 -0
  32. package/ai-docs/src/03_stream/index.md +4 -0
  33. package/ai-docs/src/04_integration/10_managed-runtime.ts +129 -0
  34. package/ai-docs/src/04_integration/index.md +5 -0
  35. package/ai-docs/src/05_batching/10_request-resolver.ts +89 -0
  36. package/ai-docs/src/05_batching/index.md +3 -0
  37. package/ai-docs/src/06_schedule/10_schedules.ts +110 -0
  38. package/ai-docs/src/06_schedule/index.md +3 -0
  39. package/ai-docs/src/07_datetime/10_creating-and-formatting.ts +30 -0
  40. package/ai-docs/src/07_datetime/20_time-zones.ts +44 -0
  41. package/ai-docs/src/07_datetime/index.md +5 -0
  42. package/ai-docs/src/08_observability/10_logging.ts +66 -0
  43. package/ai-docs/src/08_observability/20_otlp-tracing.ts +95 -0
  44. package/ai-docs/src/08_observability/index.md +7 -0
  45. package/ai-docs/src/09_testing/10_effect-tests.ts +55 -0
  46. package/ai-docs/src/09_testing/20_layer-tests.ts +138 -0
  47. package/ai-docs/src/09_testing/index.md +1 -0
  48. package/ai-docs/src/10_predicate/01_basics.ts +14 -0
  49. package/ai-docs/src/10_predicate/index.md +9 -0
  50. package/ai-docs/src/50_http-client/10_basics.ts +102 -0
  51. package/ai-docs/src/50_http-client/index.md +3 -0
  52. package/ai-docs/src/51_http-server/10_basics.ts +116 -0
  53. package/ai-docs/src/51_http-server/fixtures/api/Api.ts +14 -0
  54. package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +36 -0
  55. package/ai-docs/src/51_http-server/fixtures/api/System.ts +10 -0
  56. package/ai-docs/src/51_http-server/fixtures/api/Users.ts +91 -0
  57. package/ai-docs/src/51_http-server/fixtures/domain/User.ts +12 -0
  58. package/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +22 -0
  59. package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +36 -0
  60. package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +71 -0
  61. package/ai-docs/src/51_http-server/fixtures/server/Users.ts +62 -0
  62. package/ai-docs/src/51_http-server/index.md +3 -0
  63. package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +117 -0
  64. package/ai-docs/src/60_child-process/index.md +3 -0
  65. package/ai-docs/src/70_cli/10_basics.ts +136 -0
  66. package/ai-docs/src/70_cli/index.md +5 -0
  67. package/ai-docs/src/71_ai/10_language-model.ts +156 -0
  68. package/ai-docs/src/71_ai/20_tools.ts +226 -0
  69. package/ai-docs/src/71_ai/30_chat.ts +158 -0
  70. package/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts +9 -0
  71. package/ai-docs/src/71_ai/index.md +5 -0
  72. package/ai-docs/src/80_cluster/10_entities.ts +97 -0
  73. package/ai-docs/src/80_cluster/index.md +4 -0
  74. package/ai-docs/src/index.md +10 -0
  75. package/ai-docs/tsconfig.json +24 -0
  76. package/dist/Brand.d.ts +3 -3
  77. package/dist/Brand.d.ts.map +1 -1
  78. package/dist/Brand.js +4 -3
  79. package/dist/Brand.js.map +1 -1
  80. package/dist/Config.d.ts.map +1 -1
  81. package/dist/Config.js +2 -1
  82. package/dist/Config.js.map +1 -1
  83. package/dist/Context.d.ts +0 -1
  84. package/dist/Context.d.ts.map +1 -1
  85. package/dist/Context.js +1 -22
  86. package/dist/Context.js.map +1 -1
  87. package/dist/Cron.d.ts +31 -0
  88. package/dist/Cron.d.ts.map +1 -1
  89. package/dist/Cron.js +58 -2
  90. package/dist/Cron.js.map +1 -1
  91. package/dist/Optic.d.ts +35 -21
  92. package/dist/Optic.d.ts.map +1 -1
  93. package/dist/Optic.js +33 -34
  94. package/dist/Optic.js.map +1 -1
  95. package/dist/Schema.d.ts +73 -25
  96. package/dist/Schema.d.ts.map +1 -1
  97. package/dist/Schema.js +83 -50
  98. package/dist/Schema.js.map +1 -1
  99. package/dist/SchemaAST.d.ts +30 -0
  100. package/dist/SchemaAST.d.ts.map +1 -1
  101. package/dist/SchemaAST.js +40 -32
  102. package/dist/SchemaAST.js.map +1 -1
  103. package/dist/SchemaError.d.ts +8 -8
  104. package/dist/SchemaError.d.ts.map +1 -1
  105. package/dist/SchemaError.js +7 -6
  106. package/dist/SchemaError.js.map +1 -1
  107. package/dist/SchemaGetter.d.ts +8 -5
  108. package/dist/SchemaGetter.d.ts.map +1 -1
  109. package/dist/SchemaGetter.js +41 -37
  110. package/dist/SchemaGetter.js.map +1 -1
  111. package/dist/SchemaIssue.d.ts +195 -38
  112. package/dist/SchemaIssue.d.ts.map +1 -1
  113. package/dist/SchemaIssue.js +243 -68
  114. package/dist/SchemaIssue.js.map +1 -1
  115. package/dist/SchemaParser.d.ts +30 -0
  116. package/dist/SchemaParser.d.ts.map +1 -1
  117. package/dist/SchemaParser.js +39 -9
  118. package/dist/SchemaParser.js.map +1 -1
  119. package/dist/SchemaTransformation.d.ts +2 -2
  120. package/dist/SchemaTransformation.d.ts.map +1 -1
  121. package/dist/SchemaTransformation.js +26 -26
  122. package/dist/SchemaTransformation.js.map +1 -1
  123. package/dist/Stdio.d.ts +17 -4
  124. package/dist/Stdio.d.ts.map +1 -1
  125. package/dist/Stdio.js +6 -3
  126. package/dist/Stdio.js.map +1 -1
  127. package/dist/internal/schema/schema.js +1 -9
  128. package/dist/internal/schema/schema.js.map +1 -1
  129. package/dist/testing/TestSchema.d.ts +1 -1
  130. package/dist/testing/TestSchema.d.ts.map +1 -1
  131. package/dist/testing/TestSchema.js +8 -7
  132. package/dist/testing/TestSchema.js.map +1 -1
  133. package/dist/unstable/ai/Prompt.d.ts.map +1 -1
  134. package/dist/unstable/ai/Prompt.js +4 -4
  135. package/dist/unstable/ai/Prompt.js.map +1 -1
  136. package/dist/unstable/cluster/Reply.js +4 -4
  137. package/dist/unstable/cluster/Reply.js.map +1 -1
  138. package/dist/unstable/encoding/Msgpack.d.ts.map +1 -1
  139. package/dist/unstable/encoding/Msgpack.js +6 -6
  140. package/dist/unstable/encoding/Msgpack.js.map +1 -1
  141. package/dist/unstable/http/HttpClient.d.ts +59 -3
  142. package/dist/unstable/http/HttpClient.d.ts.map +1 -1
  143. package/dist/unstable/http/HttpClient.js +33 -19
  144. package/dist/unstable/http/HttpClient.js.map +1 -1
  145. package/dist/unstable/httpapi/HttpApiBuilder.js +10 -10
  146. package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
  147. package/dist/unstable/httpapi/HttpApiClient.js +9 -9
  148. package/dist/unstable/httpapi/HttpApiClient.js.map +1 -1
  149. package/dist/unstable/reactivity/AsyncResult.d.ts.map +1 -1
  150. package/dist/unstable/reactivity/AsyncResult.js +4 -4
  151. package/dist/unstable/reactivity/AsyncResult.js.map +1 -1
  152. package/dist/unstable/reactivity/Atom.d.ts +26 -11
  153. package/dist/unstable/reactivity/Atom.d.ts.map +1 -1
  154. package/dist/unstable/reactivity/Atom.js +9 -22
  155. package/dist/unstable/reactivity/Atom.js.map +1 -1
  156. package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
  157. package/dist/unstable/rpc/RpcClient.js +3 -1
  158. package/dist/unstable/rpc/RpcClient.js.map +1 -1
  159. package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
  160. package/dist/unstable/rpc/RpcServer.js +3 -2
  161. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  162. package/dist/unstable/workflow/Workflow.d.ts.map +1 -1
  163. package/dist/unstable/workflow/Workflow.js +2 -2
  164. package/dist/unstable/workflow/Workflow.js.map +1 -1
  165. package/package.json +5 -2
  166. package/src/Brand.ts +4 -4
  167. package/src/Config.ts +3 -1
  168. package/src/Context.ts +1 -23
  169. package/src/Cron.ts +65 -2
  170. package/src/Optic.ts +59 -58
  171. package/src/Schema.ts +145 -71
  172. package/src/SchemaAST.ts +85 -48
  173. package/src/SchemaError.ts +9 -9
  174. package/src/SchemaGetter.ts +89 -30
  175. package/src/SchemaIssue.ts +285 -80
  176. package/src/SchemaParser.ts +51 -17
  177. package/src/SchemaTransformation.ts +66 -18
  178. package/src/Stdio.ts +23 -4
  179. package/src/internal/schema/schema.ts +1 -17
  180. package/src/testing/TestSchema.ts +8 -8
  181. package/src/unstable/ai/Prompt.ts +14 -4
  182. package/src/unstable/cluster/Reply.ts +4 -4
  183. package/src/unstable/encoding/Msgpack.ts +12 -8
  184. package/src/unstable/http/HttpClient.ts +132 -23
  185. package/src/unstable/httpapi/HttpApiBuilder.ts +32 -13
  186. package/src/unstable/httpapi/HttpApiClient.ts +26 -8
  187. package/src/unstable/reactivity/AsyncResult.ts +11 -6
  188. package/src/unstable/reactivity/Atom.ts +44 -25
  189. package/src/unstable/rpc/RpcClient.ts +4 -1
  190. package/src/unstable/rpc/RpcServer.ts +4 -2
  191. package/src/unstable/workflow/Workflow.ts +2 -5
@@ -1267,7 +1267,8 @@ export declare namespace WithRateLimiter {
1267
1267
  *
1268
1268
  * **Details**
1269
1269
  *
1270
- * They define the backing limiter, initial limit window, keying strategy, algorithm, token cost, and whether response headers update future limits.
1270
+ * They define the backing limiter, initial limit window, keying strategy,
1271
+ * algorithm, token cost, retry count, and response header inspection.
1271
1272
  *
1272
1273
  * @category rate limiting
1273
1274
  * @since 4.0.0
@@ -1300,11 +1301,48 @@ export declare namespace WithRateLimiter {
1300
1301
  */
1301
1302
  readonly tokens?: number | ((request: HttpClientRequest.HttpClientRequest) => number) | undefined
1302
1303
  /**
1303
- * Disable automatic limits updates from response headers.
1304
+ * The maximum number of automatic retries after an HTTP `429` response.
1305
+ * Use a non-negative integer. Defaults to an unlimited number of retries.
1306
+ * Set to `0` to disable automatic retries.
1307
+ */
1308
+ readonly times?: number | undefined
1309
+ /**
1310
+ * Overrides the response header names used for rate limit inspection.
1311
+ * Header names are matched case-insensitively. An omitted field continues
1312
+ * using the built-in names, while a configured field replaces them for
1313
+ * that value. Header value formats are unchanged.
1314
+ */
1315
+ readonly responseHeaders?: {
1316
+ /**
1317
+ * Header containing the maximum number of requests in a window.
1318
+ */
1319
+ readonly limit?: string | undefined
1320
+ /**
1321
+ * Header containing the number of requests remaining in a window.
1322
+ */
1323
+ readonly remaining?: string | undefined
1324
+ /**
1325
+ * Header containing the rate limit reset time.
1326
+ */
1327
+ readonly reset?: string | undefined
1328
+ /**
1329
+ * Header containing the number of seconds until the rate limit resets.
1330
+ */
1331
+ readonly resetAfter?: string | undefined
1332
+ /**
1333
+ * Header containing the retry delay for an HTTP `429` response.
1334
+ */
1335
+ readonly retryAfter?: string | undefined
1336
+ } | undefined
1337
+ /**
1338
+ * Disables automatic limit updates, `Retry-After` delays, and adaptive
1339
+ * feedback from response headers. This does not disable automatic HTTP
1340
+ * `429` retries. Set `times` to `0` to disable them.
1304
1341
  */
1305
1342
  readonly disableResponseInspection?: boolean | undefined
1306
1343
  /**
1307
- * Disable adaptive learning from `Retry-After` responses.
1344
+ * Disables adaptive learning from `Retry-After` responses. Response
1345
+ * inspection and direct `Retry-After` delays remain enabled.
1308
1346
  */
1309
1347
  readonly disableAdaptiveLearning?: boolean | undefined
1310
1348
  }
@@ -1319,6 +1357,12 @@ export declare namespace WithRateLimiter {
1319
1357
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1320
1358
  * wrapping a `429` response) by forcing the retry back through the limiter.
1321
1359
  *
1360
+ * **Gotchas**
1361
+ *
1362
+ * Automatic HTTP `429` retries are unlimited unless `times` is specified.
1363
+ * Disabling response inspection does not disable retries; set `times` to `0`
1364
+ * to return or fail with the first `429`.
1365
+ *
1322
1366
  * @category rate limiting
1323
1367
  * @since 4.0.0
1324
1368
  */
@@ -1332,6 +1376,12 @@ export const withRateLimiter: {
1332
1376
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1333
1377
  * wrapping a `429` response) by forcing the retry back through the limiter.
1334
1378
  *
1379
+ * **Gotchas**
1380
+ *
1381
+ * Automatic HTTP `429` retries are unlimited unless `times` is specified.
1382
+ * Disabling response inspection does not disable retries; set `times` to `0`
1383
+ * to return or fail with the first `429`.
1384
+ *
1335
1385
  * @category rate limiting
1336
1386
  * @since 4.0.0
1337
1387
  */
@@ -1347,6 +1397,12 @@ export const withRateLimiter: {
1347
1397
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1348
1398
  * wrapping a `429` response) by forcing the retry back through the limiter.
1349
1399
  *
1400
+ * **Gotchas**
1401
+ *
1402
+ * Automatic HTTP `429` retries are unlimited unless `times` is specified.
1403
+ * Disabling response inspection does not disable retries; set `times` to `0`
1404
+ * to return or fail with the first `429`.
1405
+ *
1350
1406
  * @category rate limiting
1351
1407
  * @since 4.0.0
1352
1408
  */
@@ -1371,6 +1427,7 @@ export const withRateLimiter: {
1371
1427
  ? tokensOption
1372
1428
  : constant(tokensOption ?? 1)
1373
1429
  const adaptiveLearningEnabled = !options.disableAdaptiveLearning
1430
+ const headerNames = resolveRateLimiterHeaderNames(options.responseHeaders)
1374
1431
 
1375
1432
  const getState = (key: string): RateLimiterState => {
1376
1433
  const current = states.get(key)
@@ -1385,13 +1442,13 @@ export const withRateLimiter: {
1385
1442
  ? undefined
1386
1443
  : (clock: Clock, key: string, headers: Headers.Headers, tokens: number) => {
1387
1444
  const current = getState(key)
1388
- const next = parseRateLimiterState(current, clock, headers, tokens)
1445
+ const next = parseRateLimiterState(current, clock, headers, tokens, headerNames)
1389
1446
  if (next.limit !== current.limit || !Duration.equals(next.window, current.window)) {
1390
1447
  states.set(key, next)
1391
1448
  }
1392
1449
  }
1393
1450
 
1394
- return transform(self, function loop(effect, request): Effect.Effect<
1451
+ return transform(self, function loop(effect, request, retries = 0): Effect.Effect<
1395
1452
  HttpClientResponse.HttpClientResponse,
1396
1453
  E | RateLimiter.RateLimiterError,
1397
1454
  R
@@ -1401,11 +1458,11 @@ export const withRateLimiter: {
1401
1458
  const key = resolveKey(request)
1402
1459
  const tokens = Math.max(resolveTokens(request), 1)
1403
1460
  const current = getState(key)
1461
+ const canRetry = options.times === undefined || retries < options.times
1404
1462
  function retry(retryAfter: Duration.Duration | undefined) {
1405
- if (options.disableResponseInspection) return loop(effect, request)
1406
1463
  return retryAfter
1407
- ? Effect.flatMap(Effect.sleep(retryAfter), () => loop(effect, request))
1408
- : loop(effect, request)
1464
+ ? Effect.flatMap(Effect.sleep(retryAfter), () => loop(effect, request, retries + 1))
1465
+ : loop(effect, request, retries + 1)
1409
1466
  }
1410
1467
  const inspectResponse = (
1411
1468
  response: HttpClientResponse.HttpClientResponse,
@@ -1422,12 +1479,18 @@ export const withRateLimiter: {
1422
1479
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1423
1480
  * wrapping a `429` response) by forcing the retry back through the limiter.
1424
1481
  *
1482
+ * **Gotchas**
1483
+ *
1484
+ * Automatic HTTP `429` retries are unlimited unless `times` is specified.
1485
+ * Disabling response inspection does not disable retries; set `times` to `0`
1486
+ * to return or fail with the first `429`.
1487
+ *
1425
1488
  * @category rate limiting
1426
1489
  * @since 4.0.0
1427
1490
  */
1428
1491
  Duration.Duration | undefined>(undefined);
1429
1492
  }
1430
- const retryAfter = parseRetryAfter(clock, getHeader(response.headers, "retry-after"))
1493
+ const retryAfter = parseRetryAfter(clock, getHeader(response.headers, ...headerNames.retryAfter))
1431
1494
  if (retryAfter === undefined) {
1432
1495
  return Effect.succeed</**
1433
1496
  * Applies request rate limiting using the `RateLimiter` service.
@@ -1438,12 +1501,18 @@ export const withRateLimiter: {
1438
1501
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1439
1502
  * wrapping a `429` response) by forcing the retry back through the limiter.
1440
1503
  *
1504
+ * **Gotchas**
1505
+ *
1506
+ * Automatic HTTP `429` retries are unlimited unless `times` is specified.
1507
+ * Disabling response inspection does not disable retries; set `times` to `0`
1508
+ * to return or fail with the first `429`.
1509
+ *
1441
1510
  * @category rate limiting
1442
1511
  * @since 4.0.0
1443
1512
  */
1444
1513
  Duration.Duration | undefined>(undefined);
1445
1514
  }
1446
- const delay = parseRateLimitWindow(clock, response.headers) ?? retryAfter
1515
+ const delay = parseRateLimitWindow(clock, response.headers, headerNames) ?? retryAfter
1447
1516
  if (adaptive === undefined) {
1448
1517
  return Effect.succeed</**
1449
1518
  * Applies request rate limiting using the `RateLimiter` service.
@@ -1454,6 +1523,12 @@ export const withRateLimiter: {
1454
1523
  * automatically retries HTTP `429` responses (or `HttpClientError` values
1455
1524
  * wrapping a `429` response) by forcing the retry back through the limiter.
1456
1525
  *
1526
+ * **Gotchas**
1527
+ *
1528
+ * Automatic HTTP `429` retries are unlimited unless `times` is specified.
1529
+ * Disabling response inspection does not disable retries; set `times` to `0`
1530
+ * to return or fail with the first `429`.
1531
+ *
1457
1532
  * @category rate limiting
1458
1533
  * @since 4.0.0
1459
1534
  */
@@ -1489,7 +1564,7 @@ export const withRateLimiter: {
1489
1564
  const request = Effect.matchEffect(effect, {
1490
1565
  onSuccess(response) {
1491
1566
  return Effect.flatMap(inspectResponse(response, adaptive), (retryAfter) => {
1492
- if (response.status !== 429) return Effect.succeed(response)
1567
+ if (response.status !== 429 || !canRetry) return Effect.succeed(response)
1493
1568
  return retry(retryAfter)
1494
1569
  })
1495
1570
  },
@@ -1497,7 +1572,7 @@ export const withRateLimiter: {
1497
1572
  if (isTooManyRequestsHttpClientError(error)) {
1498
1573
  return Effect.flatMap(
1499
1574
  inspectResponse(error.reason.response, adaptive),
1500
- (retryAfter) => retry(retryAfter)
1575
+ (retryAfter) => canRetry ? retry(retryAfter) : Effect.fail(error)
1501
1576
  )
1502
1577
  }
1503
1578
  return Effect.fail(error)
@@ -1531,6 +1606,34 @@ export const withRateLimiter: {
1531
1606
  });
1532
1607
  })
1533
1608
 
1609
+ interface RateLimiterHeaderNames {
1610
+ readonly limit: ReadonlyArray<string>
1611
+ readonly remaining: ReadonlyArray<string>
1612
+ readonly reset: ReadonlyArray<string>
1613
+ readonly resetAfter: ReadonlyArray<string>
1614
+ readonly retryAfter: ReadonlyArray<string>
1615
+ }
1616
+
1617
+ const resolveRateLimiterHeaderNames = (
1618
+ responseHeaders: WithRateLimiter.Options["responseHeaders"]
1619
+ ): RateLimiterHeaderNames => ({
1620
+ limit: responseHeaders?.limit === undefined
1621
+ ? ["ratelimit-limit", "x-ratelimit-limit"]
1622
+ : [responseHeaders.limit.toLowerCase()],
1623
+ remaining: responseHeaders?.remaining === undefined
1624
+ ? ["ratelimit-remaining", "x-ratelimit-remaining"]
1625
+ : [responseHeaders.remaining.toLowerCase()],
1626
+ reset: responseHeaders?.reset === undefined
1627
+ ? ["ratelimit-reset", "x-ratelimit-reset"]
1628
+ : [responseHeaders.reset.toLowerCase()],
1629
+ resetAfter: responseHeaders?.resetAfter === undefined
1630
+ ? ["ratelimit-reset-after", "x-ratelimit-reset-after"]
1631
+ : [responseHeaders.resetAfter.toLowerCase()],
1632
+ retryAfter: responseHeaders?.retryAfter === undefined
1633
+ ? ["retry-after"]
1634
+ : [responseHeaders.retryAfter.toLowerCase()]
1635
+ })
1636
+
1534
1637
  interface RateLimiterState {
1535
1638
  readonly limit: number
1536
1639
  readonly window: Duration.Duration
@@ -1541,10 +1644,11 @@ const parseRateLimiterState = (
1541
1644
  state: RateLimiterState,
1542
1645
  clock: Clock,
1543
1646
  headers: Headers.Headers,
1544
- tokens: number
1647
+ tokens: number,
1648
+ headerNames: RateLimiterHeaderNames
1545
1649
  ): RateLimiterState => {
1546
- const limit = parseRateLimitLimit(state, headers, tokens) ?? state.limit
1547
- const window = parseRateLimitWindow(clock, headers) ?? state.window
1650
+ const limit = parseRateLimitLimit(state, headers, tokens, headerNames) ?? state.limit
1651
+ const window = parseRateLimitWindow(clock, headers, headerNames) ?? state.window
1548
1652
  if (limit === state.limit && Duration.equals(window, state.window)) {
1549
1653
  return state
1550
1654
  }
@@ -1554,35 +1658,40 @@ const parseRateLimiterState = (
1554
1658
  const parseRateLimitLimit = (
1555
1659
  state: RateLimiterState,
1556
1660
  headers: Headers.Headers,
1557
- tokens: number
1661
+ tokens: number,
1662
+ headerNames: RateLimiterHeaderNames
1558
1663
  ): number | undefined => {
1559
- const raw = getHeader(headers, "ratelimit-limit", "x-ratelimit-limit")
1664
+ const raw = getHeader(headers, ...headerNames.limit)
1560
1665
  const value = parseNumberHeader(raw)
1561
1666
  if (value !== undefined && value > 0) {
1562
1667
  return value
1563
1668
  }
1564
- const remaining = parseRateLimitRemaining(headers)
1669
+ const remaining = parseRateLimitRemaining(headers, headerNames)
1565
1670
  if (remaining === undefined) {
1566
1671
  return undefined
1567
1672
  }
1568
1673
  return state.initial ? remaining + tokens : Math.max(remaining + tokens, state.limit)
1569
1674
  }
1570
1675
 
1571
- const parseRateLimitRemaining = (headers: Headers.Headers): number | undefined => {
1572
- const raw = getHeader(headers, "ratelimit-remaining", "x-ratelimit-remaining")
1676
+ const parseRateLimitRemaining = (
1677
+ headers: Headers.Headers,
1678
+ headerNames: RateLimiterHeaderNames
1679
+ ): number | undefined => {
1680
+ const raw = getHeader(headers, ...headerNames.remaining)
1573
1681
  const value = parseNumberHeader(raw)
1574
1682
  return value !== undefined && value >= 0 ? value : undefined
1575
1683
  }
1576
1684
 
1577
1685
  const parseRateLimitWindow = (
1578
1686
  clock: Clock,
1579
- headers: Headers.Headers
1687
+ headers: Headers.Headers,
1688
+ headerNames: RateLimiterHeaderNames
1580
1689
  ): Duration.Duration | undefined => {
1581
- const resetAfter = parseResetAfter(getHeader(headers, "ratelimit-reset-after", "x-ratelimit-reset-after"))
1690
+ const resetAfter = parseResetAfter(getHeader(headers, ...headerNames.resetAfter))
1582
1691
  if (resetAfter !== undefined) {
1583
1692
  return resetAfter
1584
1693
  }
1585
- return parseResetHeader(clock, getHeader(headers, "ratelimit-reset", "x-ratelimit-reset"))
1694
+ return parseResetHeader(clock, getHeader(headers, ...headerNames.reset))
1586
1695
  }
1587
1696
 
1588
1697
  const parseRetryAfter = (
@@ -1101,10 +1101,10 @@ function toResponseErrorSchema(
1101
1101
  HttpApiSchema.isNoContent(schema.schema.ast)
1102
1102
  )
1103
1103
  const transformation = withHeadersTransformation<HttpApiSchema.withHeaders<unknown, Schema.StringTree>>(
1104
- (body) =>
1105
- encodeBody(body).pipe(
1104
+ (body, options) =>
1105
+ encodeBody(body, options).pipe(
1106
1106
  Effect.mapError((error) => error.issue),
1107
- Effect.flatMap(encodeResponse)
1107
+ Effect.flatMap((body) => encodeResponse(body, options))
1108
1108
  ),
1109
1109
  encodeHeaders
1110
1110
  )
@@ -1167,22 +1167,34 @@ function getResponseTransformation(
1167
1167
  )
1168
1168
 
1169
1169
  return SchemaTransformation.transformOrFail({
1170
- decode: () => Effect.fail(new SchemaIssue.Forbidden({ message: "Encode only schema" })),
1170
+ decode: (input, options) =>
1171
+ Effect.fail(
1172
+ new SchemaIssue.Forbidden({ message: "Encode only schema" }, input, options)
1173
+ ),
1171
1174
  encode
1172
1175
  })
1173
1176
  }
1174
1177
 
1175
1178
  function withHeadersTransformation<T = unknown>(
1176
- encodeBody: (body: unknown) => Effect.Effect<Response.HttpServerResponse, SchemaIssue.Issue, unknown>,
1177
- encodeHeaders: (headers: unknown) => Effect.Effect<unknown, Schema.SchemaError, unknown>
1179
+ encodeBody: (
1180
+ body: unknown,
1181
+ options?: SchemaAST.ParseOptions
1182
+ ) => Effect.Effect<Response.HttpServerResponse, SchemaIssue.Issue, unknown>,
1183
+ encodeHeaders: (
1184
+ headers: unknown,
1185
+ options?: SchemaAST.ParseOptions
1186
+ ) => Effect.Effect<unknown, Schema.SchemaError, unknown>
1178
1187
  ): SchemaTransformation.Transformation<T, Response.HttpServerResponse, never, unknown> {
1179
1188
  return SchemaTransformation.transformOrFail<T, Response.HttpServerResponse, never, unknown>({
1180
- decode: () => Effect.fail(new SchemaIssue.Forbidden({ message: "Encode only schema" })),
1181
- encode: (value) => {
1189
+ decode: (input, options) =>
1190
+ Effect.fail(
1191
+ new SchemaIssue.Forbidden({ message: "Encode only schema" }, input, options)
1192
+ ),
1193
+ encode: (value, options) => {
1182
1194
  const pair = value as { readonly body: unknown; readonly headers: unknown }
1183
- return Effect.flatMap(encodeBody(pair.body), (response) =>
1195
+ return Effect.flatMap(encodeBody(pair.body, options), (response) =>
1184
1196
  Effect.map(
1185
- encodeHeaders(pair.headers).pipe(Effect.mapError((error) => error.issue)),
1197
+ encodeHeaders(pair.headers, options).pipe(Effect.mapError((error) => error.issue)),
1186
1198
  (headers) => Response.setHeaders(response, headers as any)
1187
1199
  ))
1188
1200
  }
@@ -1193,10 +1205,13 @@ function getResponseEncode<E>(
1193
1205
  status: number,
1194
1206
  encoding: HttpApiSchema.ResponseEncoding,
1195
1207
  isNoContent: boolean
1196
- ): (e: E) => Effect.Effect<Response.HttpServerResponse, SchemaIssue.InvalidValue, never> {
1208
+ ): (
1209
+ e: E,
1210
+ options?: SchemaAST.ParseOptions
1211
+ ) => Effect.Effect<Response.HttpServerResponse, SchemaIssue.InvalidValue, never> {
1197
1212
  switch (encoding._tag) {
1198
1213
  case "Json": {
1199
- return ((e) => {
1214
+ return ((e, options) => {
1200
1215
  if (e === undefined || isNoContent) {
1201
1216
  return Effect.succeed(Response.empty({ status }))
1202
1217
  }
@@ -1205,7 +1220,11 @@ function getResponseEncode<E>(
1205
1220
  return Effect.succeed(Response.text(s, { status, contentType: encoding.contentType }))
1206
1221
  } catch {
1207
1222
  return Effect.fail(
1208
- new SchemaIssue.InvalidValue({ message: "Expected a JSON-serializable response body" })
1223
+ new SchemaIssue.InvalidValue(
1224
+ { expected: "a JSON-serializable response body" },
1225
+ e,
1226
+ options
1227
+ )
1209
1228
  )
1210
1229
  }
1211
1230
  })
@@ -1065,41 +1065,59 @@ function getEncodePayloadSchemaFromBody(
1065
1065
  const out = $HttpBody.pipe(Schema.decodeTo(
1066
1066
  schema,
1067
1067
  SchemaTransformation.transformOrFail<unknown, HttpBody.HttpBody>({
1068
- decode() {
1069
- return Effect.fail(new SchemaIssue.Forbidden({ message: "Encode only schema" }))
1068
+ decode(input, options) {
1069
+ return Effect.fail(
1070
+ new SchemaIssue.Forbidden({ message: "Encode only schema" }, input, options)
1071
+ )
1070
1072
  },
1071
- encode(t) {
1073
+ encode(t, options) {
1072
1074
  switch (encoding._tag) {
1073
1075
  case "Multipart":
1074
- return Effect.fail(new SchemaIssue.Forbidden({ message: "Payload must be a FormData" }))
1076
+ return Effect.fail(
1077
+ new SchemaIssue.Forbidden(
1078
+ { message: "Payload must be a FormData" },
1079
+ t,
1080
+ options
1081
+ )
1082
+ )
1075
1083
  case "Json": {
1076
1084
  try {
1077
1085
  const body = JSON.stringify(t)
1078
1086
  return Effect.succeed(HttpBody.text(body, encoding.contentType))
1079
1087
  } catch {
1080
1088
  return Effect.fail(
1081
- new SchemaIssue.InvalidValue({ message: "Expected a JSON-serializable request body" })
1089
+ new SchemaIssue.InvalidValue(
1090
+ { expected: "a JSON-serializable request body" },
1091
+ t,
1092
+ options
1093
+ )
1082
1094
  )
1083
1095
  }
1084
1096
  }
1085
1097
  case "Text": {
1086
1098
  if (typeof t !== "string") {
1087
1099
  return Effect.fail(
1088
- new SchemaIssue.InvalidValue({ message: "Expected a string" })
1100
+ new SchemaIssue.InvalidValue({ message: "Expected a string" }, t, options)
1089
1101
  )
1090
1102
  }
1091
1103
  return Effect.succeed(HttpBody.text(t, encoding.contentType))
1092
1104
  }
1093
1105
  case "FormUrlEncoded": {
1094
1106
  if (!Predicate.isObject(t)) {
1095
- return Effect.fail(new SchemaIssue.InvalidValue({ message: "Expected a record" }))
1107
+ return Effect.fail(
1108
+ new SchemaIssue.InvalidValue({ message: "Expected a record" }, t, options)
1109
+ )
1096
1110
  }
1097
1111
  return Effect.succeed(HttpBody.urlParams(UrlParams.fromInput(t as any), encoding.contentType))
1098
1112
  }
1099
1113
  case "Uint8Array": {
1100
1114
  if (!(t instanceof Uint8Array)) {
1101
1115
  return Effect.fail(
1102
- new SchemaIssue.InvalidValue({ message: "Expected a Uint8Array" })
1116
+ new SchemaIssue.InvalidValue(
1117
+ { message: "Expected a Uint8Array" },
1118
+ t,
1119
+ options
1120
+ )
1103
1121
  )
1104
1122
  }
1105
1123
  return Effect.succeed(HttpBody.uint8Array(t, encoding.contentType))
@@ -1101,7 +1101,7 @@ export const Schema = <
1101
1101
  [success_, Schema_.Cause(error, Schema_.Defect())],
1102
1102
  ([value, cause]) => (input, ast, options) => {
1103
1103
  if (!isAsyncResult(input)) {
1104
- return Effect.fail(new SchemaIssue.InvalidType(ast))
1104
+ return Effect.fail(new SchemaIssue.InvalidType(ast, input, options))
1105
1105
  }
1106
1106
  switch (input._tag) {
1107
1107
  case "Initial":
@@ -1111,7 +1111,7 @@ export const Schema = <
1111
1111
  SchemaParser.decodeUnknownEffect(value)(input.value, options),
1112
1112
  {
1113
1113
  onSuccess: (value) => success(value, input),
1114
- onFailure: (issue) => new SchemaIssue.Composite(ast, [new SchemaIssue.Pointer(["value"], issue)])
1114
+ onFailure: (issue) => SchemaIssue.makeCompositeAtKey(ast, "value", issue, input, options)
1115
1115
  }
1116
1116
  )
1117
1117
  case "Failure": {
@@ -1122,9 +1122,14 @@ export const Schema = <
1122
1122
  {
1123
1123
  onSuccess: (value) => Option.some(success<A["Type"], E["Type"]>(value, ps)),
1124
1124
  onFailure: (issue) =>
1125
- new SchemaIssue.Composite(ast, [
1126
- new SchemaIssue.Pointer(["previousSuccess", "value"], issue)
1127
- ])
1125
+ new SchemaIssue.Composite(
1126
+ ast,
1127
+ [
1128
+ new SchemaIssue.Pointer(["previousSuccess", "value"], issue)
1129
+ ],
1130
+ input,
1131
+ options
1132
+ )
1128
1133
  }
1129
1134
  )
1130
1135
  ),
@@ -1132,7 +1137,7 @@ export const Schema = <
1132
1137
  )
1133
1138
  const causeEffect = Effect.mapErrorEager(
1134
1139
  SchemaParser.decodeUnknownEffect(cause)(input.cause, options),
1135
- (issue) => new SchemaIssue.Composite(ast, [new SchemaIssue.Pointer(["cause"], issue)])
1140
+ (issue) => SchemaIssue.makeCompositeAtKey(ast, "cause", issue, input, options)
1136
1141
  )
1137
1142
  return Effect.flatMapEager(
1138
1143
  prevSuccessEffect,
@@ -790,7 +790,7 @@ export interface AtomRuntime<R, ER = never> extends Atom<AsyncResult.AsyncResult
790
790
  }
791
791
 
792
792
  /**
793
- * Factory for `AtomRuntime` values that share a `Layer.MemoMap` and a set of global layers.
793
+ * Factory for `AtomRuntime` values that share a set of global layers.
794
794
  *
795
795
  * @category models
796
796
  * @since 4.0.0
@@ -801,7 +801,6 @@ export interface RuntimeFactory {
801
801
  | Layer.Layer<R, E, AtomRegistry | Reactivity.Reactivity>
802
802
  | ((get: AtomContext) => Layer.Layer<R, E, AtomRegistry | Reactivity.Reactivity>)
803
803
  ): AtomRuntime<R, E>
804
- readonly memoMap: Layer.MemoMap
805
804
  readonly addGlobalLayer: <A, E>(layer: Layer.Layer<A, E, AtomRegistry | Reactivity.Reactivity>) => void
806
805
 
807
806
  /**
@@ -814,14 +813,40 @@ export interface RuntimeFactory {
814
813
  }
815
814
 
816
815
  /**
817
- * Creates a `RuntimeFactory` backed by the supplied `Layer.MemoMap`.
816
+ * A `RuntimeFactory` backed by an atom whose memo map is scoped to each registry.
818
817
  *
819
- * @category constructors
818
+ * @category models
820
819
  * @since 4.0.0
821
820
  */
822
- export const context: (options: {
821
+ export interface RegistryRuntimeFactory extends RuntimeFactory {
822
+ readonly memoMap: Atom<Layer.MemoMap>
823
+ }
824
+
825
+ /**
826
+ * A `RuntimeFactory` backed by a concrete memo map shared across registries.
827
+ *
828
+ * @category models
829
+ * @since 4.0.0
830
+ */
831
+ export interface SharedRuntimeFactory extends RuntimeFactory {
823
832
  readonly memoMap: Layer.MemoMap
824
- }) => RuntimeFactory = (options) => {
833
+ }
834
+
835
+ /**
836
+ * Creates a `RuntimeFactory` backed by a registry-scoped memo map by default,
837
+ * or by the supplied atom or concrete `Layer.MemoMap`.
838
+ *
839
+ * @category constructors
840
+ * @since 4.0.0
841
+ */
842
+ export function context(): RegistryRuntimeFactory
843
+ export function context(options: { readonly memoMap: Atom<Layer.MemoMap> }): RegistryRuntimeFactory
844
+ export function context(options: { readonly memoMap: Layer.MemoMap }): SharedRuntimeFactory
845
+ export function context(options?: {
846
+ readonly memoMap: Atom<Layer.MemoMap> | Layer.MemoMap
847
+ }): RegistryRuntimeFactory | SharedRuntimeFactory {
848
+ const memoMap = options?.memoMap ?? removeTtl(make(() => Layer.makeMemoMapUnsafe()))
849
+ const resolveMemoMap = (get: AtomContext): Layer.MemoMap => isAtom(memoMap) ? get(memoMap) : memoMap
825
850
  let globalLayer: Layer.Layer<any, any, AtomRegistry> = Reactivity.layer
826
851
  function factory<E, R>(
827
852
  create:
@@ -843,23 +868,25 @@ export const context: (options: {
843
868
 
844
869
  self.read = function read(get: AtomContext) {
845
870
  const layer = get(layerAtom)
846
- const build = Effect.flatMap(Effect.scope, (scope) => Layer.buildWithMemoMap(layer, options.memoMap, scope))
871
+ const build = Effect.flatMap(Effect.scope, (scope) => Layer.buildWithMemoMap(layer, resolveMemoMap(get), scope))
847
872
  return effect(get, build, { uninterruptible: true })
848
873
  }
849
874
 
850
875
  return self
851
876
  }
852
- factory.memoMap = options.memoMap
877
+ factory.memoMap = memoMap
853
878
  factory.addGlobalLayer = (layer: Layer.Layer<any, any, AtomRegistry | Reactivity.Reactivity>) => {
854
879
  globalLayer = Layer.provideMerge(globalLayer, Layer.provide(layer, Reactivity.layer))
855
880
  }
856
- const reactivityAtom = removeTtl(make(
857
- Effect.contextWith((services: Context.Context<Scope.Scope>) =>
858
- Layer.buildWithMemoMap(Reactivity.layer, options.memoMap, Context.get(services, Scope.Scope))
859
- ).pipe(
860
- Effect.map(Context.get(Reactivity.Reactivity))
881
+ const reactivityAtom = removeTtl(
882
+ make((get) =>
883
+ Effect.contextWith((services: Context.Context<Scope.Scope>) =>
884
+ Layer.buildWithMemoMap(Reactivity.layer, resolveMemoMap(get), Context.get(services, Scope.Scope))
885
+ ).pipe(
886
+ Effect.map(Context.get(Reactivity.Reactivity))
887
+ )
861
888
  )
862
- ))
889
+ )
863
890
  factory.withReactivity =
864
891
  (keys: ReadonlyArray<unknown> | ReadonlyRecord<string, ReadonlyArray<unknown>>) =>
865
892
  <A extends Atom<any>>(atom: A): A =>
@@ -871,24 +898,16 @@ export const context: (options: {
871
898
  get.subscribe(atom, (value) => get.setSelf(value))
872
899
  return get.once(atom)
873
900
  }, { initialValueTarget: atom }) as any as A
874
- return factory
901
+ return factory as any
875
902
  }
876
903
 
877
904
  /**
878
- * Default `Layer.MemoMap` used by the module-level `runtime` factory.
879
- *
880
- * @category context
881
- * @since 4.0.0
882
- */
883
- export const defaultMemoMap: Layer.MemoMap = Layer.makeMemoMapUnsafe()
884
-
885
- /**
886
- * Default `RuntimeFactory` created with `defaultMemoMap`.
905
+ * Default registry-scoped `RuntimeFactory`.
887
906
  *
888
907
  * @category context
889
908
  * @since 4.0.0
890
909
  */
891
- export const runtime: RuntimeFactory = context({ memoMap: defaultMemoMap })
910
+ export const runtime: RegistryRuntimeFactory = context()
892
911
 
893
912
  /**
894
913
  * Returns `Rx.runtime.withReactivity` for refreshing an atom whenever the
@@ -23,6 +23,7 @@ import * as Latch from "../../Latch.ts"
23
23
  import * as Layer from "../../Layer.ts"
24
24
  import * as Option from "../../Option.ts"
25
25
  import * as Pool from "../../Pool.ts"
26
+ import * as Predicate from "../../Predicate.ts"
26
27
  import * as Queue from "../../Queue.ts"
27
28
  import * as Result from "../../Result.ts"
28
29
  import * as Schedule from "../../Schedule.ts"
@@ -51,6 +52,8 @@ import * as RpcSerialization from "./RpcSerialization.ts"
51
52
  import * as RpcWorker from "./RpcWorker.ts"
52
53
  import { withRunClient } from "./Utils.ts"
53
54
 
55
+ const isRpcClientError = (u: unknown): u is RpcClientError => Predicate.isTagged(u, "RpcClientError")
56
+
54
57
  /**
55
58
  * The object-shaped client generated from a union of RPC definitions, with one
56
59
  * method per RPC tag.
@@ -974,7 +977,7 @@ export const makeProtocolHttp = (client: HttpClient.HttpClient): Effect.Effect<
974
977
  })
975
978
  })
976
979
  )).pipe(
977
- Effect.mapError((cause) => cause instanceof RpcClientError ? cause : httpClientError(cause))
980
+ Effect.mapError((cause) => isRpcClientError(cause) ? cause : httpClientError(cause))
978
981
  )
979
982
  if (!hasResponse) {
980
983
  return yield* emptyResponseError(request)