effect 4.1.1 → 4.1.2

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 (142) hide show
  1. package/ai-docs/package.json +2 -2
  2. package/dist/ByteSize.d.ts.map +1 -1
  3. package/dist/ByteSize.js +27 -14
  4. package/dist/ByteSize.js.map +1 -1
  5. package/dist/Iterable.d.ts.map +1 -1
  6. package/dist/Iterable.js +27 -24
  7. package/dist/Iterable.js.map +1 -1
  8. package/dist/JsonSchema.d.ts.map +1 -1
  9. package/dist/JsonSchema.js +27 -6
  10. package/dist/JsonSchema.js.map +1 -1
  11. package/dist/MutableRef.d.ts.map +1 -1
  12. package/dist/MutableRef.js +0 -7
  13. package/dist/MutableRef.js.map +1 -1
  14. package/dist/Ref.d.ts.map +1 -1
  15. package/dist/Ref.js +0 -8
  16. package/dist/Ref.js.map +1 -1
  17. package/dist/Schedule.d.ts.map +1 -1
  18. package/dist/Schedule.js +8 -3
  19. package/dist/Schedule.js.map +1 -1
  20. package/dist/Stream.d.ts.map +1 -1
  21. package/dist/Stream.js +6 -5
  22. package/dist/Stream.js.map +1 -1
  23. package/dist/ai/McpServer.d.ts.map +1 -1
  24. package/dist/ai/McpServer.js +61 -33
  25. package/dist/ai/McpServer.js.map +1 -1
  26. package/dist/ai/internal/mcpProtocol/v2026_07_28.js +4 -3
  27. package/dist/ai/internal/mcpProtocol/v2026_07_28.js.map +1 -1
  28. package/dist/ai/internal/structured-output.js +11 -2
  29. package/dist/ai/internal/structured-output.js.map +1 -1
  30. package/dist/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  31. package/dist/cluster/ClusterWorkflowEngine.js +18 -9
  32. package/dist/cluster/ClusterWorkflowEngine.js.map +1 -1
  33. package/dist/cluster/Entity.d.ts +26 -0
  34. package/dist/cluster/Entity.d.ts.map +1 -1
  35. package/dist/cluster/Entity.js.map +1 -1
  36. package/dist/cluster/MessageStorage.d.ts +6 -6
  37. package/dist/cluster/MessageStorage.d.ts.map +1 -1
  38. package/dist/cluster/MessageStorage.js +1 -3
  39. package/dist/cluster/MessageStorage.js.map +1 -1
  40. package/dist/cluster/Runners.d.ts.map +1 -1
  41. package/dist/cluster/Runners.js +13 -2
  42. package/dist/cluster/Runners.js.map +1 -1
  43. package/dist/cluster/Sharding.d.ts +13 -0
  44. package/dist/cluster/Sharding.d.ts.map +1 -1
  45. package/dist/cluster/Sharding.js.map +1 -1
  46. package/dist/cluster/internal/clusterAbandon.d.ts.map +1 -1
  47. package/dist/cluster/internal/clusterAbandon.js +6 -4
  48. package/dist/cluster/internal/clusterAbandon.js.map +1 -1
  49. package/dist/cluster/internal/entityManager.js +48 -4
  50. package/dist/cluster/internal/entityManager.js.map +1 -1
  51. package/dist/cluster/internal/transactionNotifications.d.ts +5 -0
  52. package/dist/cluster/internal/transactionNotifications.d.ts.map +1 -0
  53. package/dist/cluster/internal/transactionNotifications.js +5 -0
  54. package/dist/cluster/internal/transactionNotifications.js.map +1 -0
  55. package/dist/encoding/Json5.d.ts +44 -0
  56. package/dist/encoding/Json5.d.ts.map +1 -0
  57. package/dist/encoding/Json5.js +56 -0
  58. package/dist/encoding/Json5.js.map +1 -0
  59. package/dist/encoding/Jsonc.d.ts +47 -0
  60. package/dist/encoding/Jsonc.d.ts.map +1 -0
  61. package/dist/encoding/Jsonc.js +120 -0
  62. package/dist/encoding/Jsonc.js.map +1 -0
  63. package/dist/encoding/SchemaBinary.d.ts +94 -0
  64. package/dist/encoding/SchemaBinary.d.ts.map +1 -1
  65. package/dist/encoding/SchemaBinary.js +219 -6
  66. package/dist/encoding/SchemaBinary.js.map +1 -1
  67. package/dist/encoding/index.d.ts +10 -0
  68. package/dist/encoding/index.d.ts.map +1 -1
  69. package/dist/encoding/index.js +10 -0
  70. package/dist/encoding/index.js.map +1 -1
  71. package/dist/eventlog/EventLogServer.d.ts.map +1 -1
  72. package/dist/eventlog/EventLogServer.js +28 -12
  73. package/dist/eventlog/EventLogServer.js.map +1 -1
  74. package/dist/http/HttpClient.d.ts +15 -9
  75. package/dist/http/HttpClient.d.ts.map +1 -1
  76. package/dist/http/HttpClient.js +18 -11
  77. package/dist/http/HttpClient.js.map +1 -1
  78. package/dist/internal/dateTime.js +17 -3
  79. package/dist/internal/dateTime.js.map +1 -1
  80. package/dist/internal/jsonParser.d.ts +5 -0
  81. package/dist/internal/jsonParser.d.ts.map +1 -0
  82. package/dist/internal/jsonParser.js +280 -0
  83. package/dist/internal/jsonParser.js.map +1 -0
  84. package/dist/internal/persistence.js +8 -1
  85. package/dist/internal/persistence.js.map +1 -1
  86. package/dist/internal/sql.js +16 -0
  87. package/dist/internal/sql.js.map +1 -1
  88. package/dist/internal/version.js +1 -1
  89. package/dist/observability/OtlpExporter.d.ts +4 -0
  90. package/dist/observability/OtlpExporter.d.ts.map +1 -1
  91. package/dist/observability/OtlpExporter.js +40 -12
  92. package/dist/observability/OtlpExporter.js.map +1 -1
  93. package/dist/persistence/RateLimiter.d.ts +7 -0
  94. package/dist/persistence/RateLimiter.d.ts.map +1 -1
  95. package/dist/persistence/RateLimiter.js +13 -4
  96. package/dist/persistence/RateLimiter.js.map +1 -1
  97. package/dist/rpc/RpcServer.d.ts.map +1 -1
  98. package/dist/rpc/RpcServer.js +10 -1
  99. package/dist/rpc/RpcServer.js.map +1 -1
  100. package/dist/sql/SqlClient.d.ts +8 -0
  101. package/dist/sql/SqlClient.d.ts.map +1 -1
  102. package/dist/sql/SqlClient.js +36 -12
  103. package/dist/sql/SqlClient.js.map +1 -1
  104. package/dist/sql/Statement.d.ts +5 -0
  105. package/dist/sql/Statement.d.ts.map +1 -1
  106. package/dist/sql/Statement.js +8 -6
  107. package/dist/sql/Statement.js.map +1 -1
  108. package/package.json +4 -4
  109. package/src/ByteSize.ts +29 -14
  110. package/src/Iterable.ts +20 -14
  111. package/src/JsonSchema.ts +45 -7
  112. package/src/MutableRef.ts +1 -8
  113. package/src/Ref.ts +0 -8
  114. package/src/Schedule.ts +8 -6
  115. package/src/Stream.ts +6 -5
  116. package/src/ai/McpServer.ts +66 -43
  117. package/src/ai/internal/mcpProtocol/v2026_07_28.ts +4 -5
  118. package/src/ai/internal/structured-output.ts +10 -4
  119. package/src/cluster/ClusterWorkflowEngine.ts +29 -28
  120. package/src/cluster/Entity.ts +52 -0
  121. package/src/cluster/MessageStorage.ts +3 -6
  122. package/src/cluster/Runners.ts +16 -2
  123. package/src/cluster/Sharding.ts +13 -0
  124. package/src/cluster/internal/clusterAbandon.ts +7 -7
  125. package/src/cluster/internal/entityManager.ts +65 -5
  126. package/src/cluster/internal/transactionNotifications.ts +7 -0
  127. package/src/encoding/Json5.ts +59 -0
  128. package/src/encoding/Jsonc.ts +126 -0
  129. package/src/encoding/SchemaBinary.ts +228 -7
  130. package/src/encoding/index.ts +12 -0
  131. package/src/eventlog/EventLogServer.ts +36 -12
  132. package/src/http/HttpClient.ts +34 -22
  133. package/src/internal/dateTime.ts +26 -3
  134. package/src/internal/jsonParser.ts +307 -0
  135. package/src/internal/persistence.ts +9 -1
  136. package/src/internal/sql.ts +25 -0
  137. package/src/internal/version.ts +1 -1
  138. package/src/observability/OtlpExporter.ts +42 -18
  139. package/src/persistence/RateLimiter.ts +13 -4
  140. package/src/rpc/RpcServer.ts +12 -1
  141. package/src/sql/SqlClient.ts +48 -14
  142. package/src/sql/Statement.ts +8 -13
@@ -972,6 +972,139 @@ export function fieldId(id: number) {
972
972
 
973
973
  const FIELD_ID_ANNOTATION_KEY = "~effect/encoding/SchemaBinary/fieldId"
974
974
 
975
+ // Marks the check that opts a number or bigint into a fixed-width encoding.
976
+ // It lives on a check, not the AST, so later annotations and checks keep it.
977
+ const FIXED_WIDTH_ANNOTATION_KEY = "~effect/encoding/SchemaBinary/fixedWidth"
978
+
979
+ type FixedWidthKind = "i32" | "u32" | "i64" | "u64"
980
+
981
+ const BIGINT_I64_MIN = BigInt("-9223372036854775808")
982
+ const BIGINT_I64_MAX = BigInt("9223372036854775807")
983
+ const BIGINT_U64_MAX = BigInt("18446744073709551615")
984
+
985
+ /**
986
+ * A 32-bit signed integer written as four little-endian bytes.
987
+ *
988
+ * **When to use**
989
+ *
990
+ * Use when values regularly need more than four bytes as a varint, such as
991
+ * hashes or full-range ids. Small values are cheaper as `Schema.Int`.
992
+ *
993
+ * **Details**
994
+ *
995
+ * Rejects non-integers and values outside -2147483648 through 2147483647
996
+ * instead of truncating them. `-0` is written as `0`.
997
+ *
998
+ * **Gotchas**
999
+ *
1000
+ * Switching a field between this, {@link uint32}, `Schema.Number` or
1001
+ * `Schema.Int` changes its encoding, so it is not a compatible evolution. In
1002
+ * default mode the other reader can decode a different value without an error,
1003
+ * for example after a signedness swap or inside an array of structs.
1004
+ * Fingerprint mode rejects the layout mismatch.
1005
+ *
1006
+ * **Example** (Declaring fixed-width integer fields)
1007
+ *
1008
+ * ```ts
1009
+ * import { Schema } from "effect"
1010
+ * import { SchemaBinary } from "effect/encoding"
1011
+ *
1012
+ * const Packet = Schema.Struct({
1013
+ * version: SchemaBinary.uint32,
1014
+ * offset: SchemaBinary.int32,
1015
+ * sequence: SchemaBinary.uint64,
1016
+ * timestamp: SchemaBinary.int64
1017
+ * })
1018
+ * ```
1019
+ *
1020
+ * @stability unstable
1021
+ * @category schemas
1022
+ * @since 4.0.0
1023
+ */
1024
+ export const int32: Schema.Number = Schema.Number.check(
1025
+ Schema.isInt32({ [FIXED_WIDTH_ANNOTATION_KEY]: "i32" })
1026
+ )
1027
+
1028
+ /**
1029
+ * A 32-bit unsigned integer written as four little-endian bytes.
1030
+ *
1031
+ * **Details**
1032
+ *
1033
+ * Rejects non-integers and values outside 0 through 4294967295 instead of
1034
+ * wrapping them. See {@link int32} for when a fixed width pays off.
1035
+ *
1036
+ * @stability unstable
1037
+ * @category schemas
1038
+ * @since 4.0.0
1039
+ */
1040
+ export const uint32: Schema.Number = Schema.Number.check(
1041
+ Schema.isUint32({ [FIXED_WIDTH_ANNOTATION_KEY]: "u32" })
1042
+ )
1043
+
1044
+ /**
1045
+ * A 64-bit signed integer, as a `bigint`, written as eight little-endian bytes.
1046
+ *
1047
+ * **Details**
1048
+ *
1049
+ * Rejects values outside -2^63 through 2^63 - 1 instead of wrapping them. As a
1050
+ * struct field it costs one length byte more than the payload, because the
1051
+ * eight-byte field kind belongs to floating-point numbers.
1052
+ *
1053
+ * **Gotchas**
1054
+ *
1055
+ * Switching a field between this, {@link uint64} or `Schema.BigInt` is not a
1056
+ * compatible evolution. All three are length-prefixed struct fields, and eight
1057
+ * bytes can be valid for each, so a default-mode reader may decode the other
1058
+ * form as a different value without an error. Fingerprint mode rejects the
1059
+ * layout mismatch.
1060
+ *
1061
+ * @stability unstable
1062
+ * @category schemas
1063
+ * @since 4.0.0
1064
+ */
1065
+ export const int64: Schema.BigInt = Schema.BigInt.check(
1066
+ Schema.isBetweenBigInt({ minimum: BIGINT_I64_MIN, maximum: BIGINT_I64_MAX }, {
1067
+ expected: "a 64-bit integer",
1068
+ [FIXED_WIDTH_ANNOTATION_KEY]: "i64"
1069
+ })
1070
+ )
1071
+
1072
+ /**
1073
+ * A 64-bit unsigned integer, as a `bigint`, written as eight little-endian
1074
+ * bytes.
1075
+ *
1076
+ * **Details**
1077
+ *
1078
+ * Rejects values outside 0 through 2^64 - 1 instead of wrapping them. See
1079
+ * {@link int64} for the struct field cost.
1080
+ *
1081
+ * **Gotchas**
1082
+ *
1083
+ * Switching a field between this, {@link int64} or `Schema.BigInt` can decode
1084
+ * as a different value without an error in default mode; see {@link int64}.
1085
+ *
1086
+ * @stability unstable
1087
+ * @category schemas
1088
+ * @since 4.0.0
1089
+ */
1090
+ export const uint64: Schema.BigInt = Schema.BigInt.check(
1091
+ Schema.isBetweenBigInt({ minimum: BigInt(0), maximum: BIGINT_U64_MAX }, {
1092
+ expected: "a 64-bit unsigned integer",
1093
+ [FIXED_WIDTH_ANNOTATION_KEY]: "u64"
1094
+ })
1095
+ )
1096
+
1097
+ // The fixed width the schema opted into, from any of its checks.
1098
+ function fixedWidthKind(ast: SchemaAST.AST): FixedWidthKind | undefined {
1099
+ const checks = ast.checks
1100
+ if (checks === undefined) return undefined
1101
+ for (const check of checks) {
1102
+ const kind = check.annotations?.[FIXED_WIDTH_ANNOTATION_KEY]
1103
+ if (kind !== undefined) return kind as FixedWidthKind
1104
+ }
1105
+ return undefined
1106
+ }
1107
+
975
1108
  // Bit 0 selects fingerprint mode; all other flag bits are reserved.
976
1109
  const ENVELOPE = 0x20 // version nibble 2, flags 0
977
1110
  const ENVELOPE_FINGERPRINT = 0x21 // version nibble 2, flag bit 0
@@ -1370,6 +1503,11 @@ class Writer {
1370
1503
  this.view.setBigInt64(this.start + this.len, n, true)
1371
1504
  this.len += 8
1372
1505
  }
1506
+ u64(n: bigint) {
1507
+ this.ensure(8)
1508
+ this.view.setBigUint64(this.start + this.len, n, true)
1509
+ this.len += 8
1510
+ }
1373
1511
  u32le(n: number) {
1374
1512
  this.ensure(4)
1375
1513
  this.view.setUint32(this.start + this.len, n >>> 0, true)
@@ -1801,6 +1939,12 @@ class Reader {
1801
1939
  this.pos += 8
1802
1940
  return value
1803
1941
  }
1942
+ u64(): bigint {
1943
+ if (this.pos + 8 > this.end) invalid("complete value", undefined, this.options)
1944
+ const value = (this.view ?? this.dataView()).getBigUint64(this.pos, true)
1945
+ this.pos += 8
1946
+ return value
1947
+ }
1804
1948
  u32le(): number {
1805
1949
  if (this.pos + 4 > this.end) invalid("complete value", undefined, this.options)
1806
1950
  const value = (this.view ?? this.dataView()).getUint32(this.pos, true)
@@ -1825,6 +1969,7 @@ type LeafKind =
1825
1969
  | "symbol"
1826
1970
  | "bytes"
1827
1971
  | "bigint"
1972
+ | FixedWidthKind
1828
1973
  | "json"
1829
1974
  | "duration"
1830
1975
  | "bigDecimal"
@@ -2007,7 +2152,12 @@ function packedSize(layout: Layout): number | undefined {
2007
2152
  return packedSize(layout.leaf)
2008
2153
  case "bool":
2009
2154
  return 1
2155
+ case "i32":
2156
+ case "u32":
2157
+ return 4
2010
2158
  case "int64":
2159
+ case "i64":
2160
+ case "u64":
2011
2161
  return 8
2012
2162
  default:
2013
2163
  return undefined
@@ -2318,10 +2468,15 @@ function compileLayout(root: SchemaAST.AST): CompiledLayout {
2318
2468
  case "Undefined":
2319
2469
  case "Void":
2320
2470
  return leaf("undefined")
2321
- case "Number":
2471
+ case "Number": {
2472
+ const fixed = fixedWidthKind(ast)
2473
+ if (fixed === "i32" || fixed === "u32") return leaf(fixed)
2322
2474
  return leaf(provesInteger(ast) ? "int" : "number")
2323
- case "BigInt":
2324
- return leaf("bigint")
2475
+ }
2476
+ case "BigInt": {
2477
+ const fixed = fixedWidthKind(ast)
2478
+ return leaf(fixed === "i64" || fixed === "u64" ? fixed : "bigint")
2479
+ }
2325
2480
  case "Literal":
2326
2481
  return literalLayout(leaf(literalKind(ast.literal)), [ast.literal])
2327
2482
  case "Unknown":
@@ -2685,7 +2840,7 @@ function isExact(root: SchemaAST.AST): boolean {
2685
2840
  const walked = new Map<SchemaAST.AST, boolean>()
2686
2841
  const exact = (ast: SchemaAST.AST): boolean => {
2687
2842
  if (
2688
- ast.encoding !== undefined || ast.checks !== undefined ||
2843
+ ast.encoding !== undefined || (ast.checks !== undefined && !isFixedWidthOnly(ast)) ||
2689
2844
  (ast as { readonly encodingChecks?: SchemaAST.Checks }).encodingChecks !== undefined
2690
2845
  ) {
2691
2846
  return false
@@ -2746,6 +2901,13 @@ function isExact(root: SchemaAST.AST): boolean {
2746
2901
  return exact(root)
2747
2902
  }
2748
2903
 
2904
+ // The binary layer enforces a fixed-width check on its own, so it is exact
2905
+ // when no other check rides along.
2906
+ function isFixedWidthOnly(ast: SchemaAST.AST): boolean {
2907
+ return (ast._tag === "Number" || ast._tag === "BigInt") && ast.checks?.length === 1 &&
2908
+ fixedWidthKind(ast) !== undefined
2909
+ }
2910
+
2749
2911
  // A clean Exit whose success schema is exact: the binary layer fully produces
2750
2912
  // and validates success exits, so only failure exits need the schema pass for
2751
2913
  // their cause's error and defect encodings.
@@ -2784,7 +2946,11 @@ const F = {
2784
2946
  exit: 22,
2785
2947
  cause: 23,
2786
2948
  causeReason: 24,
2787
- literal: 25
2949
+ literal: 25,
2950
+ i32: 26,
2951
+ u32: 27,
2952
+ i64: 28,
2953
+ u64: 29
2788
2954
  } as const
2789
2955
 
2790
2956
  // Hash the compiled layout graph, including cycle back-edge distances.
@@ -2828,6 +2994,10 @@ function layoutFingerprint(root: Layout): bigint {
2828
2994
  case "symbol":
2829
2995
  case "bytes":
2830
2996
  case "bigint":
2997
+ case "i32":
2998
+ case "u32":
2999
+ case "i64":
3000
+ case "u64":
2831
3001
  case "json":
2832
3002
  case "duration":
2833
3003
  case "bigDecimal":
@@ -2986,12 +3156,16 @@ function matchesLayout(layout: Layout, value: unknown): boolean {
2986
3156
  return value === undefined
2987
3157
  case "number":
2988
3158
  case "int":
3159
+ case "i32":
3160
+ case "u32":
2989
3161
  return typeof value === "number"
2990
3162
  case "string":
2991
3163
  return typeof value === "string"
2992
3164
  case "symbol":
2993
3165
  return typeof value === "symbol"
2994
3166
  case "bigint":
3167
+ case "i64":
3168
+ case "u64":
2995
3169
  return typeof value === "bigint"
2996
3170
  case "bytes":
2997
3171
  return value instanceof Uint8Array
@@ -3287,6 +3461,10 @@ function valueWireCode(layout: Layout, value: unknown, options: SchemaAST.ParseO
3287
3461
  case "int":
3288
3462
  if (!Number.isSafeInteger(value)) encodeFail("an integer", value, options)
3289
3463
  return FIELD_WIRE_VARINT
3464
+ // The payload write validates the value.
3465
+ case "i32":
3466
+ case "u32":
3467
+ return FIELD_WIRE_FIXED32
3290
3468
  default:
3291
3469
  return FIELD_WIRE_SIZED
3292
3470
  }
@@ -3295,8 +3473,8 @@ function valueWireCode(layout: Layout, value: unknown, options: SchemaAST.ParseO
3295
3473
  function encodeWirePayload(ctx: EncodeContext, layout: Layout, value: unknown, wire: number, w: Writer) {
3296
3474
  const kind = wire % FIELD_WIRE_FACTOR
3297
3475
  switch (kind) {
3298
- // EMPTY and FIXED32 are decoder-side skip support only; `valueWireCode`
3299
- // never produces them.
3476
+ // EMPTY is decoder-side skip support only; `valueWireCode` never produces
3477
+ // it.
3300
3478
  case FIELD_WIRE_FALSE:
3301
3479
  case FIELD_WIRE_TRUE:
3302
3480
  return
@@ -3306,6 +3484,9 @@ function encodeWirePayload(ctx: EncodeContext, layout: Layout, value: unknown, w
3306
3484
  case FIELD_WIRE_FIXED64:
3307
3485
  w.f64(value as number)
3308
3486
  return
3487
+ case FIELD_WIRE_FIXED32:
3488
+ encodeValue(ctx, layout, value, w)
3489
+ return
3309
3490
  case FIELD_WIRE_DECIMAL: {
3310
3491
  const scale = Math.floor(wire / FIELD_WIRE_FACTOR)
3311
3492
  w.numberVarint(Math.round((value as number) * POW10[scale]))
@@ -4010,6 +4191,29 @@ function encodeValue(ctx: EncodeContext, layout: Layout, value: unknown, w: Writ
4010
4191
  if (typeof value !== "bigint") encodeFail("a bigint", value, ctx.options)
4011
4192
  w.zigzag(value)
4012
4193
  return
4194
+ // `disableChecks` can bypass the range check used to select these layouts.
4195
+ case "i32":
4196
+ if (typeof value !== "number" || (value | 0) !== value) encodeFail("a 32-bit integer", value, ctx.options)
4197
+ w.i32le(value)
4198
+ return
4199
+ case "u32":
4200
+ if (typeof value !== "number" || value >>> 0 !== value) {
4201
+ encodeFail("a 32-bit unsigned integer", value, ctx.options)
4202
+ }
4203
+ w.u32le(value)
4204
+ return
4205
+ case "i64":
4206
+ if (typeof value !== "bigint" || value < BIGINT_I64_MIN || value > BIGINT_I64_MAX) {
4207
+ encodeFail("a 64-bit integer", value, ctx.options)
4208
+ }
4209
+ w.i64(value)
4210
+ return
4211
+ case "u64":
4212
+ if (typeof value !== "bigint" || value < BIGINT_ZERO || value > BIGINT_U64_MAX) {
4213
+ encodeFail("a 64-bit unsigned integer", value, ctx.options)
4214
+ }
4215
+ w.u64(value)
4216
+ return
4013
4217
  case "int64": {
4014
4218
  if (!matchesLayout(layout, value)) {
4015
4219
  encodeFail(layout.flavor === "date" ? "a Date" : "a DateTime.Utc", value, ctx.options)
@@ -4375,6 +4579,9 @@ function fieldWireMask(layout: Layout): number {
4375
4579
  return (1 << FIELD_WIRE_VARINT) | (1 << FIELD_WIRE_FIXED64) | (1 << FIELD_WIRE_DECIMAL)
4376
4580
  case "int":
4377
4581
  return 1 << FIELD_WIRE_VARINT
4582
+ case "i32":
4583
+ case "u32":
4584
+ return 1 << FIELD_WIRE_FIXED32
4378
4585
  default:
4379
4586
  return 1 << FIELD_WIRE_SIZED
4380
4587
  }
@@ -4406,6 +4613,8 @@ function decodeFieldPayload(layout: Layout, wireMask: number, kind: number, r: R
4406
4613
  skipFieldPayload(kind, r)
4407
4614
  return ABSENT
4408
4615
  }
4616
+ // Only the two 32-bit layouts accept this kind, so the payload needs no window.
4617
+ if (kind === FIELD_WIRE_FIXED32) return layout._ === "u32" ? r.u32le() : r.i32le()
4409
4618
  if (kind !== FIELD_WIRE_SIZED) {
4410
4619
  const value = decodeScalarField(kind, r)
4411
4620
  if (layout._ === "literal" && !hasLiteral(layout, value)) {
@@ -4993,6 +5202,18 @@ function decodeValueUnchecked(layout: Layout, r: Reader): unknown {
4993
5202
  return r.takeCopy(r.end - r.pos)
4994
5203
  case "bigint":
4995
5204
  return r.zigzag()
5205
+ case "i32":
5206
+ if (r.remaining !== 4) invalid("a 32-bit integer", undefined, r.options)
5207
+ return r.i32le()
5208
+ case "u32":
5209
+ if (r.remaining !== 4) invalid("a 32-bit unsigned integer", undefined, r.options)
5210
+ return r.u32le()
5211
+ case "i64":
5212
+ if (r.remaining !== 8) invalid("a 64-bit integer", undefined, r.options)
5213
+ return r.i64()
5214
+ case "u64":
5215
+ if (r.remaining !== 8) invalid("a 64-bit unsigned integer", undefined, r.options)
5216
+ return r.u64()
4996
5217
  case "int64": {
4997
5218
  if (r.remaining !== 8) invalid("int64", undefined, r.options)
4998
5219
  const millis = Number(r.i64())
@@ -41,12 +41,24 @@ export * as Hex from "./Hex.ts"
41
41
  */
42
42
  export * as Ini from "./Ini.ts"
43
43
 
44
+ /**
45
+ * @stability unstable
46
+ * @since 4.1.2
47
+ */
48
+ export * as Json5 from "./Json5.ts"
49
+
44
50
  /**
45
51
  * @stability unstable
46
52
  * @since 4.0.6
47
53
  */
48
54
  export * as JsonArray from "./JsonArray.ts"
49
55
 
56
+ /**
57
+ * @stability unstable
58
+ * @since 4.1.2
59
+ */
60
+ export * as Jsonc from "./Jsonc.ts"
61
+
50
62
  /**
51
63
  * @stability unstable
52
64
  * @since 4.0.0
@@ -12,6 +12,7 @@
12
12
  import type { NonEmptyReadonlyArray } from "../Array.ts"
13
13
  import * as Arr from "../Array.ts"
14
14
  import * as Cache from "../Cache.ts"
15
+ import * as Clock from "../Clock.ts"
15
16
  import * as Context from "../Context.ts"
16
17
  import * as Mac from "../crypto/Mac.ts"
17
18
  import * as Data from "../Data.ts"
@@ -22,7 +23,6 @@ import * as Exit from "../Exit.ts"
22
23
  import * as Hash from "../Hash.ts"
23
24
  import * as Uuid from "../internal/uuid.ts"
24
25
  import * as Layer from "../Layer.ts"
25
- import * as Option from "../Option.ts"
26
26
  import * as Redacted from "../Redacted.ts"
27
27
  import type * as Rpc from "../rpc/Rpc.ts"
28
28
  import type * as RpcGroup from "../rpc/RpcGroup.ts"
@@ -119,11 +119,6 @@ export const layerRpcHandlers = (options: {
119
119
  Rpc.ToHandler<RpcGroup.Rpcs<typeof EventLogRemoteRpcs>> | EventLogAuthentication
120
120
  > =>
121
121
  EventLogRemoteRpcs.toLayer(Effect.gen(function*() {
122
- const clientChallenges = yield* Cache.make({
123
- lookup: (_clientId: number) => Effect.orDie(EventLogSessionAuth.makeSessionAuthChallenge),
124
- capacity: Number.MAX_SAFE_INTEGER,
125
- timeToLive: EventLogSessionAuth.SessionAuthChallengeTimeToLiveMillis
126
- })
127
122
  const toFrames = makeFramer()
128
123
 
129
124
  const persistedSigningPublicKeys = yield* Cache.makeWith(
@@ -148,17 +143,31 @@ export const layerRpcHandlers = (options: {
148
143
 
149
144
  return EventLogRemoteRpcs.of({
150
145
  "EventLog.Hello": Effect.fnUntraced(function*(_, { client }) {
151
- const challenge = yield* Cache.get(clientChallenges, client.id)
146
+ // Kept on the connection, so it is released when the client disconnects
147
+ // and shared by overlapping Hellos until `Authenticate` consumes it.
148
+ let pending = getPendingChallenge(client, yield* Clock.currentTimeMillis)
149
+ if (pending === undefined) {
150
+ const challenge = yield* Effect.orDie(EventLogSessionAuth.makeSessionAuthChallenge)
151
+ // Read the clock again, as generating the challenge can suspend. A
152
+ // concurrent Hello may have stored a challenge in the meantime.
153
+ const now = yield* Clock.currentTimeMillis
154
+ pending = getPendingChallenge(client, now) ?? {
155
+ challenge,
156
+ expiresAt: now + EventLogSessionAuth.SessionAuthChallengeTimeToLiveMillis
157
+ }
158
+ client.annotate(PendingChallenge, pending)
159
+ }
152
160
  return new HelloResponse({
153
161
  remoteId: options.remoteId,
154
- challenge,
162
+ challenge: pending.challenge,
155
163
  firstSequence: options.firstSequence,
156
164
  compaction: options.compaction === true ? true : undefined
157
165
  })
158
166
  }),
159
167
  "EventLog.Authenticate": Effect.fnUntraced(function*(request, { client }) {
160
- const challenge = Option.getOrNull(yield* Cache.getOption(clientChallenges, client.id))
161
- if (!challenge) {
168
+ const pending = getPendingChallenge(client, yield* Clock.currentTimeMillis)
169
+ client.annotate(PendingChallenge, undefined)
170
+ if (pending === undefined) {
162
171
  return yield* new EventLogProtocolError({
163
172
  requestTag: "Authenticate",
164
173
  publicKey: request.publicKey,
@@ -166,13 +175,12 @@ export const layerRpcHandlers = (options: {
166
175
  message: "Session auth challenge has expired"
167
176
  })
168
177
  }
169
- yield* Cache.invalidate(clientChallenges, client.id)
170
178
  if (options.authorizeIdentity) {
171
179
  yield* options.authorizeIdentity(request.publicKey, request.signingPublicKey)
172
180
  }
173
181
  const verified = yield* EventLogSessionAuth.verifySessionAuthenticateRequest({
174
182
  remoteId: options.remoteId,
175
- challenge,
183
+ challenge: pending.challenge,
176
184
  publicKey: request.publicKey,
177
185
  signingPublicKey: request.signingPublicKey,
178
186
  signature: request.signature,
@@ -409,6 +417,22 @@ class ConnectionWritesState extends Context.Service<ConnectionWritesState, Conne
409
417
  "effect/eventlog/EventLogServer/ConnectionWrites"
410
418
  ) {}
411
419
 
420
+ // The connection's unconsumed `Hello` challenge, cleared by `Authenticate`.
421
+ const PendingChallenge = Context.Reference<
422
+ | {
423
+ readonly challenge: Uint8Array<ArrayBuffer>
424
+ readonly expiresAt: number
425
+ }
426
+ | undefined
427
+ >("effect/eventlog/EventLogServer/PendingChallenge", {
428
+ defaultValue: () => undefined
429
+ })
430
+
431
+ const getPendingChallenge = (client: Rpc.ServerClient, now: number) => {
432
+ const pending = Context.get(client.annotations, PendingChallenge)
433
+ return pending !== undefined && pending.expiresAt > now ? pending : undefined
434
+ }
435
+
412
436
  const getConnectionWrites = (client: Rpc.ServerClient): ConnectionWrites => {
413
437
  const existing = Context.getOrUndefined(client.annotations, ConnectionWritesState)
414
438
  if (existing !== undefined) return existing
@@ -2666,9 +2666,11 @@ export const tapRequest: {
2666
2666
  * **Details**
2667
2667
  *
2668
2668
  * Response cookies are merged into the ref with `Cookies.mergeIntoArray` for
2669
- * the response URL. Before each request, `Cookies.toRequestCookieHeader` selects
2670
- * the cookies for the request URL and sets them as the `cookie` header. Expiry
2671
- * uses the current `Clock`.
2669
+ * the response URL. When a request is sent, `Cookies.toRequestCookieHeader`
2670
+ * selects the cookies for its final URL, after later transformations such as
2671
+ * `HttpClientRequest.prependUrl`, and sets them as the `cookie` header. With
2672
+ * `followRedirects` applied after `withCookiesRef`, this runs for every hop.
2673
+ * Expiry uses the current `Clock`.
2672
2674
  *
2673
2675
  * @stability unstable
2674
2676
  * @category cookies
@@ -2687,9 +2689,11 @@ export const withCookiesRef: {
2687
2689
  * **Details**
2688
2690
  *
2689
2691
  * Response cookies are merged into the ref with `Cookies.mergeIntoArray` for
2690
- * the response URL. Before each request, `Cookies.toRequestCookieHeader` selects
2691
- * the cookies for the request URL and sets them as the `cookie` header. Expiry
2692
- * uses the current `Clock`.
2692
+ * the response URL. When a request is sent, `Cookies.toRequestCookieHeader`
2693
+ * selects the cookies for its final URL, after later transformations such as
2694
+ * `HttpClientRequest.prependUrl`, and sets them as the `cookie` header. With
2695
+ * `followRedirects` applied after `withCookiesRef`, this runs for every hop.
2696
+ * Expiry uses the current `Clock`.
2693
2697
  *
2694
2698
  * @stability unstable
2695
2699
  * @category cookies
@@ -2708,9 +2712,11 @@ export const withCookiesRef: {
2708
2712
  * **Details**
2709
2713
  *
2710
2714
  * Response cookies are merged into the ref with `Cookies.mergeIntoArray` for
2711
- * the response URL. Before each request, `Cookies.toRequestCookieHeader` selects
2712
- * the cookies for the request URL and sets them as the `cookie` header. Expiry
2713
- * uses the current `Clock`.
2715
+ * the response URL. When a request is sent, `Cookies.toRequestCookieHeader`
2716
+ * selects the cookies for its final URL, after later transformations such as
2717
+ * `HttpClientRequest.prependUrl`, and sets them as the `cookie` header. With
2718
+ * `followRedirects` applied after `withCookiesRef`, this runs for every hop.
2719
+ * Expiry uses the current `Clock`.
2714
2720
  *
2715
2721
  * @stability unstable
2716
2722
  * @category cookies
@@ -2722,21 +2728,27 @@ export const withCookiesRef: {
2722
2728
  <E, R>(
2723
2729
  self: HttpClient.With<E, R>,
2724
2730
  ref: Ref.Ref<ReadonlyArray<Cookies.Cookie>>
2725
- ): HttpClient.With<E, R> =>
2726
- makeWith(
2731
+ ): HttpClient.With<E, R> => {
2732
+ const now = () => Fiber.getCurrent()!.getRef(Clock).currentTimeMillisUnsafe()
2733
+ const addCookies = (request: HttpClientRequest.HttpClientRequest) =>
2734
+ Effect.map(Ref.get(ref), (cookies) => {
2735
+ const header = Cookies.toRequestCookieHeader(cookies, request.url, now())
2736
+ if (header === undefined) return request
2737
+ const withCookies = HttpClientRequest.setHeader(request, "cookie", header)
2738
+ // setHeader returns a new request, so carry over the withScope controller
2739
+ const controller = scopedRequests.get(request)
2740
+ if (controller !== undefined) scopedRequests.set(withCookies, controller)
2741
+ return withCookies
2742
+ })
2743
+ return makeWith(
2727
2744
  (request: Effect.Effect<HttpClientRequest.HttpClientRequest, E, R>) =>
2728
- Effect.tap(self.postprocess(request), (response) => {
2729
- const now = Fiber.getCurrent()!.getRef(Clock).currentTimeMillisUnsafe()
2730
- return Ref.update(ref, Cookies.mergeIntoArray(response.url, response.cookies, now))
2731
- }),
2732
- (request) =>
2733
- Effect.flatMap(self.preprocess(request), (request) =>
2734
- Effect.map(Ref.get(ref), (cookies) => {
2735
- const now = Fiber.getCurrent()!.getRef(Clock).currentTimeMillisUnsafe()
2736
- const header = Cookies.toRequestCookieHeader(cookies, request.url, now)
2737
- return header === undefined ? request : HttpClientRequest.setHeader(request, "cookie", header)
2738
- }))
2745
+ Effect.tap(
2746
+ self.postprocess(Effect.flatMap(request, addCookies)),
2747
+ (response) => Ref.update(ref, Cookies.mergeIntoArray(response.url, response.cookies, now()))
2748
+ ),
2749
+ self.preprocess
2739
2750
  )
2751
+ }
2740
2752
  )
2741
2753
 
2742
2754
  /**
@@ -1054,6 +1054,29 @@ export const subtract: {
1054
1054
  return add(self, newParts)
1055
1055
  })
1056
1056
 
1057
+ const mutateRounding = (
1058
+ self: DateTime.DateTime,
1059
+ part: DateTime.DateTime.UnitSingular,
1060
+ f: (date: Date) => void,
1061
+ disambiguation: DateTime.Disambiguation = "compatible"
1062
+ ): DateTime.DateTime => {
1063
+ if (
1064
+ self._tag === "Utc" || self.zone._tag === "Offset" ||
1065
+ part === "day" || part === "week" || part === "month" || part === "year"
1066
+ ) {
1067
+ return mutate(self, f, { disambiguation })
1068
+ }
1069
+ // keep the input's offset so rounding stays in the same occurrence of a repeated hour
1070
+ const date = toDate(self)
1071
+ const offset = date.getTime() - Math.trunc(self.epochMilliseconds)
1072
+ f(date)
1073
+ const adjustedMillis = date.getTime()
1074
+ const result = makeZonedProto(adjustedMillis - offset, self.zone)
1075
+ return zonedOffset(result) === offset
1076
+ ? result
1077
+ : makeZonedFromAdjusted(adjustedMillis, self.zone, disambiguation)
1078
+ }
1079
+
1057
1080
  const startOfDate = (date: Date, part: DateTime.DateTime.UnitSingular, options?: {
1058
1081
  readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1059
1082
  }) => {
@@ -1105,7 +1128,7 @@ export const startOf: {
1105
1128
  }): A
1106
1129
  } = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: {
1107
1130
  readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1108
- }): DateTime.DateTime => mutate(self, (date) => startOfDate(date, part, options)))
1131
+ }): DateTime.DateTime => mutateRounding(self, part, (date) => startOfDate(date, part, options)))
1109
1132
 
1110
1133
  const endOfDate = (date: Date, part: DateTime.DateTime.UnitSingular, options?: {
1111
1134
  readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
@@ -1158,7 +1181,7 @@ export const endOf: {
1158
1181
  }): A
1159
1182
  } = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: {
1160
1183
  readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1161
- }): DateTime.DateTime => mutate(self, (date) => endOfDate(date, part, options)))
1184
+ }): DateTime.DateTime => mutateRounding(self, part, (date) => endOfDate(date, part, options), "later"))
1162
1185
 
1163
1186
  /** @internal */
1164
1187
  export const nearest: {
@@ -1171,7 +1194,7 @@ export const nearest: {
1171
1194
  } = dual(isDateTimeArgs, (self: DateTime.DateTime, part: DateTime.DateTime.UnitSingular, options?: {
1172
1195
  readonly weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined
1173
1196
  }): DateTime.DateTime =>
1174
- mutate(self, (date) => {
1197
+ mutateRounding(self, part, (date) => {
1175
1198
  if (part === "millisecond") return
1176
1199
  const millis = date.getTime()
1177
1200
  const start = new Date(millis)