effect 4.0.0-beta.105 → 4.0.0-beta.107

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 (181) hide show
  1. package/dist/Channel.d.ts +26 -0
  2. package/dist/Channel.d.ts.map +1 -1
  3. package/dist/Channel.js +47 -0
  4. package/dist/Channel.js.map +1 -1
  5. package/dist/Config.js +3 -3
  6. package/dist/Config.js.map +1 -1
  7. package/dist/ConfigProvider.d.ts +35 -3
  8. package/dist/ConfigProvider.d.ts.map +1 -1
  9. package/dist/ConfigProvider.js +41 -8
  10. package/dist/ConfigProvider.js.map +1 -1
  11. package/dist/Duration.d.ts.map +1 -1
  12. package/dist/Duration.js +13 -1
  13. package/dist/Duration.js.map +1 -1
  14. package/dist/Fiber.d.ts +1 -1
  15. package/dist/Fiber.d.ts.map +1 -1
  16. package/dist/Function.d.ts +26 -0
  17. package/dist/Function.d.ts.map +1 -1
  18. package/dist/Function.js +36 -0
  19. package/dist/Function.js.map +1 -1
  20. package/dist/JsonSchema.d.ts.map +1 -1
  21. package/dist/JsonSchema.js +34 -17
  22. package/dist/JsonSchema.js.map +1 -1
  23. package/dist/Schema.d.ts +32 -84
  24. package/dist/Schema.d.ts.map +1 -1
  25. package/dist/Schema.js +58 -54
  26. package/dist/Schema.js.map +1 -1
  27. package/dist/SchemaAST.d.ts +2 -17
  28. package/dist/SchemaAST.d.ts.map +1 -1
  29. package/dist/SchemaAST.js +39 -28
  30. package/dist/SchemaAST.js.map +1 -1
  31. package/dist/SchemaRepresentation.d.ts +44 -7
  32. package/dist/SchemaRepresentation.d.ts.map +1 -1
  33. package/dist/SchemaRepresentation.js +15 -4
  34. package/dist/SchemaRepresentation.js.map +1 -1
  35. package/dist/ScopedRef.d.ts.map +1 -1
  36. package/dist/ScopedRef.js +1 -1
  37. package/dist/ScopedRef.js.map +1 -1
  38. package/dist/Stream.d.ts +10 -0
  39. package/dist/Stream.d.ts.map +1 -1
  40. package/dist/Stream.js +12 -23
  41. package/dist/Stream.js.map +1 -1
  42. package/dist/TxQueue.js +1 -1
  43. package/dist/TxQueue.js.map +1 -1
  44. package/dist/internal/effect.js +2 -4
  45. package/dist/internal/effect.js.map +1 -1
  46. package/dist/internal/executionPlan.js +3 -2
  47. package/dist/internal/executionPlan.js.map +1 -1
  48. package/dist/internal/rcRef.js +18 -13
  49. package/dist/internal/rcRef.js.map +1 -1
  50. package/dist/internal/schema/fromJsonSchemaDocument.js +32 -16
  51. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  52. package/dist/internal/schema/toArbitrary.d.ts +1 -4
  53. package/dist/internal/schema/toArbitrary.d.ts.map +1 -1
  54. package/dist/internal/schema/toArbitrary.js +0 -74
  55. package/dist/internal/schema/toArbitrary.js.map +1 -1
  56. package/dist/internal/schema/toJsonSchemaDocument.js +18 -4
  57. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  58. package/dist/internal/schema/toRepresentation.js +45 -43
  59. package/dist/internal/schema/toRepresentation.js.map +1 -1
  60. package/dist/testing/TestSchema.js +2 -2
  61. package/dist/testing/TestSchema.js.map +1 -1
  62. package/dist/unstable/ai/McpSchema.d.ts +128 -128
  63. package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
  64. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  65. package/dist/unstable/ai/McpServer.js +23 -11
  66. package/dist/unstable/ai/McpServer.js.map +1 -1
  67. package/dist/unstable/ai/Prompt.d.ts +4 -3
  68. package/dist/unstable/ai/Prompt.d.ts.map +1 -1
  69. package/dist/unstable/ai/Prompt.js +38 -13
  70. package/dist/unstable/ai/Prompt.js.map +1 -1
  71. package/dist/unstable/cli/Argument.d.ts +6 -3
  72. package/dist/unstable/cli/Argument.d.ts.map +1 -1
  73. package/dist/unstable/cli/Argument.js +2 -1
  74. package/dist/unstable/cli/Argument.js.map +1 -1
  75. package/dist/unstable/cli/CliError.d.ts +20 -1
  76. package/dist/unstable/cli/CliError.d.ts.map +1 -1
  77. package/dist/unstable/cli/CliError.js +26 -2
  78. package/dist/unstable/cli/CliError.js.map +1 -1
  79. package/dist/unstable/cli/Command.d.ts +23 -0
  80. package/dist/unstable/cli/Command.d.ts.map +1 -1
  81. package/dist/unstable/cli/Command.js +23 -4
  82. package/dist/unstable/cli/Command.js.map +1 -1
  83. package/dist/unstable/cluster/Entity.d.ts +2 -2
  84. package/dist/unstable/cluster/Entity.d.ts.map +1 -1
  85. package/dist/unstable/cluster/Entity.js.map +1 -1
  86. package/dist/unstable/cluster/EntityProxy.d.ts +3 -3
  87. package/dist/unstable/cluster/EntityProxy.d.ts.map +1 -1
  88. package/dist/unstable/cluster/EntityProxy.js +4 -3
  89. package/dist/unstable/cluster/EntityProxy.js.map +1 -1
  90. package/dist/unstable/cluster/Sharding.d.ts +2 -2
  91. package/dist/unstable/cluster/Sharding.d.ts.map +1 -1
  92. package/dist/unstable/cluster/Sharding.js +14 -5
  93. package/dist/unstable/cluster/Sharding.js.map +1 -1
  94. package/dist/unstable/eventlog/EventLogEncryption.d.ts +6 -5
  95. package/dist/unstable/eventlog/EventLogEncryption.d.ts.map +1 -1
  96. package/dist/unstable/eventlog/EventLogEncryption.js +14 -12
  97. package/dist/unstable/eventlog/EventLogEncryption.js.map +1 -1
  98. package/dist/unstable/eventlog/EventLogMessage.d.ts +3 -3
  99. package/dist/unstable/eventlog/EventLogMessage.d.ts.map +1 -1
  100. package/dist/unstable/eventlog/EventLogMessage.js +2 -3
  101. package/dist/unstable/eventlog/EventLogMessage.js.map +1 -1
  102. package/dist/unstable/eventlog/EventLogRemote.js +4 -4
  103. package/dist/unstable/eventlog/EventLogRemote.js.map +1 -1
  104. package/dist/unstable/eventlog/EventLogServerEncrypted.js +3 -2
  105. package/dist/unstable/eventlog/EventLogServerEncrypted.js.map +1 -1
  106. package/dist/unstable/http/HttpServerRequest.d.ts.map +1 -1
  107. package/dist/unstable/http/HttpServerRequest.js +7 -1
  108. package/dist/unstable/http/HttpServerRequest.js.map +1 -1
  109. package/dist/unstable/http/Multipart.d.ts +2 -0
  110. package/dist/unstable/http/Multipart.d.ts.map +1 -1
  111. package/dist/unstable/http/Multipart.js +4 -12
  112. package/dist/unstable/http/Multipart.js.map +1 -1
  113. package/dist/unstable/http/MultipartParser/internal/headers.d.ts.map +1 -1
  114. package/dist/unstable/http/MultipartParser/internal/headers.js +16 -2
  115. package/dist/unstable/http/MultipartParser/internal/headers.js.map +1 -1
  116. package/dist/unstable/http/MultipartParser/internal/multipart.d.ts.map +1 -1
  117. package/dist/unstable/http/MultipartParser/internal/multipart.js +23 -8
  118. package/dist/unstable/http/MultipartParser/internal/multipart.js.map +1 -1
  119. package/dist/unstable/httpapi/OpenApi.d.ts.map +1 -1
  120. package/dist/unstable/httpapi/OpenApi.js +1 -1
  121. package/dist/unstable/httpapi/OpenApi.js.map +1 -1
  122. package/dist/unstable/process/ChildProcess.d.ts +8 -0
  123. package/dist/unstable/process/ChildProcess.d.ts.map +1 -1
  124. package/dist/unstable/process/ChildProcess.js.map +1 -1
  125. package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
  126. package/dist/unstable/rpc/RpcClient.js +5 -0
  127. package/dist/unstable/rpc/RpcClient.js.map +1 -1
  128. package/dist/unstable/sql/Migrator.d.ts +9 -1
  129. package/dist/unstable/sql/Migrator.d.ts.map +1 -1
  130. package/dist/unstable/sql/Migrator.js +15 -2
  131. package/dist/unstable/sql/Migrator.js.map +1 -1
  132. package/dist/unstable/sql/SqlResolver.d.ts.map +1 -1
  133. package/dist/unstable/sql/SqlResolver.js +20 -16
  134. package/dist/unstable/sql/SqlResolver.js.map +1 -1
  135. package/dist/unstable/workers/Worker.d.ts.map +1 -1
  136. package/dist/unstable/workers/Worker.js +11 -12
  137. package/dist/unstable/workers/Worker.js.map +1 -1
  138. package/package.json +1 -1
  139. package/src/Channel.ts +58 -0
  140. package/src/Config.ts +3 -3
  141. package/src/ConfigProvider.ts +43 -8
  142. package/src/Duration.ts +12 -1
  143. package/src/Fiber.ts +1 -1
  144. package/src/Function.ts +37 -0
  145. package/src/JsonSchema.ts +43 -42
  146. package/src/Schema.ts +68 -146
  147. package/src/SchemaAST.ts +50 -30
  148. package/src/SchemaRepresentation.ts +44 -7
  149. package/src/ScopedRef.ts +3 -1
  150. package/src/Stream.ts +12 -30
  151. package/src/TxQueue.ts +1 -1
  152. package/src/internal/effect.ts +3 -6
  153. package/src/internal/executionPlan.ts +3 -2
  154. package/src/internal/rcRef.ts +24 -16
  155. package/src/internal/schema/fromJsonSchemaDocument.ts +36 -18
  156. package/src/internal/schema/toArbitrary.ts +0 -72
  157. package/src/internal/schema/toJsonSchemaDocument.ts +24 -4
  158. package/src/internal/schema/toRepresentation.ts +66 -48
  159. package/src/testing/TestSchema.ts +2 -2
  160. package/src/unstable/ai/McpServer.ts +23 -11
  161. package/src/unstable/ai/Prompt.ts +37 -15
  162. package/src/unstable/cli/Argument.ts +6 -3
  163. package/src/unstable/cli/CliError.ts +28 -2
  164. package/src/unstable/cli/Command.ts +44 -4
  165. package/src/unstable/cluster/Entity.ts +7 -2
  166. package/src/unstable/cluster/EntityProxy.ts +12 -5
  167. package/src/unstable/cluster/Sharding.ts +29 -17
  168. package/src/unstable/eventlog/EventLogEncryption.ts +15 -16
  169. package/src/unstable/eventlog/EventLogMessage.ts +2 -3
  170. package/src/unstable/eventlog/EventLogRemote.ts +4 -4
  171. package/src/unstable/eventlog/EventLogServerEncrypted.ts +2 -2
  172. package/src/unstable/http/HttpServerRequest.ts +8 -3
  173. package/src/unstable/http/Multipart.ts +4 -13
  174. package/src/unstable/http/MultipartParser/internal/headers.ts +16 -387
  175. package/src/unstable/http/MultipartParser/internal/multipart.ts +25 -8
  176. package/src/unstable/httpapi/OpenApi.ts +4 -1
  177. package/src/unstable/process/ChildProcess.ts +8 -0
  178. package/src/unstable/rpc/RpcClient.ts +5 -0
  179. package/src/unstable/sql/Migrator.ts +22 -9
  180. package/src/unstable/sql/SqlResolver.ts +28 -20
  181. package/src/unstable/workers/Worker.ts +13 -14
@@ -489,7 +489,7 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
489
489
  const isHttp = Option.isSome(yield* Effect.serviceOption(HttpRouter.HttpRouter))
490
490
  const sessions = protocolState.sessions
491
491
  const clientProtocols = new Map<number, McpProtocol.ProtocolAdapter>()
492
- const cancelledRequests = new Map<number, Set<string>>()
492
+ const activeRequests = new Map<number, Map<string, boolean>>()
493
493
  const handlers = yield* Layer.build(layerHandlers(options, {
494
494
  sessions,
495
495
  protocolRegistry
@@ -577,11 +577,13 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
577
577
  ...protocol,
578
578
  send: (clientId, response) => {
579
579
  if (response._tag === "Exit") {
580
- const requests = cancelledRequests.get(clientId)
581
- if (requests?.delete(requestKey(response.requestId)) === true) {
582
- if (requests.size === 0) {
583
- cancelledRequests.delete(clientId)
584
- }
580
+ const requests = activeRequests.get(clientId)
581
+ const key = requestKey(response.requestId)
582
+ const cancelled = requests?.get(key)
583
+ if (requests !== undefined && requests.delete(key) && requests.size === 0) {
584
+ activeRequests.delete(clientId)
585
+ }
586
+ if (cancelled === true) {
585
587
  return Effect.void
586
588
  }
587
589
  }
@@ -670,9 +672,12 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
670
672
  if (request.tag === "notifications/cancelled") {
671
673
  return decodeCancelledNotification(request.payload).pipe(
672
674
  Effect.flatMap(({ requestId }) => {
673
- const requests = cancelledRequests.get(clientId) ?? new Set<string>()
674
- requests.add(requestKey(requestId))
675
- cancelledRequests.set(clientId, requests)
675
+ const key = requestKey(requestId)
676
+ const requests = activeRequests.get(clientId)
677
+ if (requests?.has(key) !== true) {
678
+ return Effect.void
679
+ }
680
+ requests.set(key, true)
676
681
  return f(clientId, {
677
682
  _tag: "Interrupt",
678
683
  requestId: RpcMessage.RequestId(requestId)
@@ -732,7 +737,14 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
732
737
  }
733
738
  return selectedProtocol.payloadCodecs(rpc).decode(request.payload).pipe(
734
739
  Effect.matchEffect({
735
- onSuccess: () => f(clientId, routedRequest),
740
+ onSuccess: () => {
741
+ if (request.isNotification !== true) {
742
+ const requests = activeRequests.get(clientId) ?? new Map<string, boolean>()
743
+ requests.set(requestKey(request.id), false)
744
+ activeRequests.set(clientId, requests)
745
+ }
746
+ return f(clientId, routedRequest)
747
+ },
736
748
  onFailure: () =>
737
749
  request.isNotification
738
750
  ? Effect.void
@@ -752,7 +764,7 @@ const runWithProtocolState = Effect.fnUntraced(function*(options: {
752
764
  case "Interrupt":
753
765
  return f(clientId, request)
754
766
  case "Eof":
755
- cancelledRequests.delete(clientId)
767
+ activeRequests.delete(clientId)
756
768
  return f(clientId, request)
757
769
  case "Pong":
758
770
  case "Exit":
@@ -2004,11 +2004,23 @@ export const make = (input: RawInput): Prompt => {
2004
2004
  */
2005
2005
  export const fromMessages = (messages: ReadonlyArray<Message>): Prompt => makePrompt(messages)
2006
2006
 
2007
+ const mergeOptions = (left: ProviderOptions, right: ProviderOptions): ProviderOptions => {
2008
+ const result: Record<string, ProviderOptions[string]> = { ...left }
2009
+ for (const [provider, metadata] of Object.entries(right)) {
2010
+ const previous = result[provider]
2011
+ result[provider] = Predicate.isObject(previous) && Predicate.isObject(metadata)
2012
+ ? Object.assign({}, previous, metadata)
2013
+ : metadata
2014
+ }
2015
+ return result
2016
+ }
2017
+
2007
2018
  /**
2008
2019
  * Creates a `Prompt` from response parts by folding completed text and
2009
- * reasoning streams into assistant parts, placing tool calls and approval
2010
- * requests in an assistant message, and placing non-preliminary tool results
2011
- * in a tool message using their encoded results.
2020
+ * reasoning streams into assistant parts, preserving provider metadata as
2021
+ * prompt options, placing tool calls and approval requests in an assistant
2022
+ * message, and placing non-preliminary tool results in a tool message using
2023
+ * their encoded results.
2012
2024
  *
2013
2025
  * **Example** (Creating prompts from response parts)
2014
2026
  *
@@ -2052,55 +2064,63 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
2052
2064
  const assistantParts: Array<AssistantMessagePart> = []
2053
2065
  const toolParts: Array<ToolMessagePart> = []
2054
2066
 
2055
- const activeTextDeltas = new Map<string, { text: string }>()
2056
- const activeReasoningDeltas = new Map<string, { text: string }>()
2067
+ const activeTextDeltas = new Map<string, { text: string; options: ProviderOptions }>()
2068
+ const activeReasoningDeltas = new Map<string, { text: string; options: ProviderOptions }>()
2057
2069
 
2058
2070
  for (const part of parts) {
2059
2071
  switch (part.type) {
2060
2072
  // Text Parts
2061
2073
  case "text": {
2062
- assistantParts.push(makePart("text", { text: part.text }))
2074
+ assistantParts.push(makePart("text", { text: part.text, options: part.metadata }))
2063
2075
  break
2064
2076
  }
2065
2077
 
2066
2078
  // Text Parts (streaming)
2067
2079
  case "text-start": {
2068
- activeTextDeltas.set(part.id, { text: "" })
2080
+ activeTextDeltas.set(part.id, { text: "", options: part.metadata })
2069
2081
  break
2070
2082
  }
2071
2083
  case "text-delta": {
2072
2084
  if (activeTextDeltas.has(part.id)) {
2073
- activeTextDeltas.get(part.id)!.text += part.delta
2085
+ const active = activeTextDeltas.get(part.id)!
2086
+ active.text += part.delta
2087
+ active.options = mergeOptions(active.options, part.metadata)
2074
2088
  }
2075
2089
  break
2076
2090
  }
2077
2091
  case "text-end": {
2078
2092
  if (activeTextDeltas.has(part.id)) {
2079
- assistantParts.push(makePart("text", activeTextDeltas.get(part.id)!))
2093
+ const active = activeTextDeltas.get(part.id)!
2094
+ active.options = mergeOptions(active.options, part.metadata)
2095
+ assistantParts.push(makePart("text", active))
2080
2096
  }
2081
2097
  break
2082
2098
  }
2083
2099
 
2084
2100
  // Reasoning Parts
2085
2101
  case "reasoning": {
2086
- assistantParts.push(makePart("reasoning", { text: part.text }))
2102
+ assistantParts.push(makePart("reasoning", { text: part.text, options: part.metadata }))
2087
2103
  break
2088
2104
  }
2089
2105
 
2090
2106
  // Reasoning Parts (streaming)
2091
2107
  case "reasoning-start": {
2092
- activeReasoningDeltas.set(part.id, { text: "" })
2108
+ activeReasoningDeltas.set(part.id, { text: "", options: part.metadata })
2093
2109
  break
2094
2110
  }
2095
2111
  case "reasoning-delta": {
2096
2112
  if (activeReasoningDeltas.has(part.id)) {
2097
- activeReasoningDeltas.get(part.id)!.text += part.delta
2113
+ const active = activeReasoningDeltas.get(part.id)!
2114
+ active.text += part.delta
2115
+ active.options = mergeOptions(active.options, part.metadata)
2098
2116
  }
2099
2117
  break
2100
2118
  }
2101
2119
  case "reasoning-end": {
2102
2120
  if (activeReasoningDeltas.has(part.id)) {
2103
- assistantParts.push(makePart("reasoning", activeReasoningDeltas.get(part.id)!))
2121
+ const active = activeReasoningDeltas.get(part.id)!
2122
+ active.options = mergeOptions(active.options, part.metadata)
2123
+ assistantParts.push(makePart("reasoning", active))
2104
2124
  }
2105
2125
  break
2106
2126
  }
@@ -2111,7 +2131,8 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
2111
2131
  id: part.id,
2112
2132
  name: part.name,
2113
2133
  params: part.params,
2114
- providerExecuted: part.providerExecuted ?? false
2134
+ providerExecuted: part.providerExecuted ?? false,
2135
+ options: part.metadata
2115
2136
  }))
2116
2137
  break
2117
2138
  }
@@ -2125,7 +2146,8 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
2125
2146
  name: part.name,
2126
2147
  isFailure: part.isFailure,
2127
2148
  result: part.encodedResult,
2128
- providerExecuted: part.providerExecuted ?? false
2149
+ providerExecuted: part.providerExecuted ?? false,
2150
+ options: part.metadata
2129
2151
  }))
2130
2152
  }
2131
2153
  break
@@ -719,7 +719,8 @@ export const map: {
719
719
  * ? Effect.succeed(file)
720
720
  * : Effect.fail(
721
721
  * new CliError.UserError({
722
- * cause: new Error("Only .txt files allowed")
722
+ * cause: new Error(`Unsupported file extension: ${file}`),
723
+ * userMessage: "Only .txt files allowed"
723
724
  * })
724
725
  * )
725
726
  * )
@@ -768,7 +769,8 @@ export const mapEffect: {
768
769
  * ? Effect.succeed(file)
769
770
  * : Effect.fail(
770
771
  * new CliError.UserError({
771
- * cause: new Error("Only .txt files allowed")
772
+ * cause: new Error(`Unsupported file extension: ${file}`),
773
+ * userMessage: "Only .txt files allowed"
772
774
  * })
773
775
  * )
774
776
  * )
@@ -817,7 +819,8 @@ export const mapEffect: {
817
819
  * ? Effect.succeed(file)
818
820
  * : Effect.fail(
819
821
  * new CliError.UserError({
820
- * cause: new Error("Only .txt files allowed")
822
+ * cause: new Error(`Unsupported file extension: ${file}`),
823
+ * userMessage: "Only .txt files allowed"
821
824
  * })
822
825
  * )
823
826
  * )
@@ -478,6 +478,10 @@ export class UnknownSubcommand extends Schema.TaggedError<UnknownSubcommand>(
478
478
  /**
479
479
  * Error wrapper for user handler failures in the CLI error channel.
480
480
  *
481
+ * `userMessage` can provide safe, user-facing text independently of the
482
+ * underlying cause. When omitted or empty, `message` uses a non-empty string
483
+ * cause or `Error.message`, then falls back to `"An error occurred"`.
484
+ *
481
485
  * **Example** (Wrapping user errors)
482
486
  *
483
487
  * ```ts import.meta.vitest
@@ -486,7 +490,8 @@ export class UnknownSubcommand extends Schema.TaggedError<UnknownSubcommand>(
486
490
  *
487
491
  * // Wrapping user errors
488
492
  * const userError = new CliError.UserError({
489
- * cause: new Error("Database connection failed")
493
+ * cause: new Error("Database connection failed for postgres://localhost"),
494
+ * userMessage: "Could not connect to the database"
490
495
  * })
491
496
  *
492
497
  * // In command handler
@@ -516,7 +521,8 @@ export class UnknownSubcommand extends Schema.TaggedError<UnknownSubcommand>(
516
521
  export class UserError extends Schema.TaggedError<UserError>(
517
522
  `${TypeId}/UserError`
518
523
  )("UserError", {
519
- cause: Schema.Defect()
524
+ cause: Schema.Defect(),
525
+ userMessage: Schema.optionalKey(Schema.String)
520
526
  }) {
521
527
  /**
522
528
  * Marks this value as a user handler error for runtime guards.
@@ -524,6 +530,26 @@ export class UserError extends Schema.TaggedError<UserError>(
524
530
  * @since 4.0.0
525
531
  */
526
532
  readonly [TypeId] = TypeId
533
+
534
+ /**
535
+ * Controls whether the runtime logger should report this error. The CLI
536
+ * runner sets this to `false` after rendering the error itself.
537
+ *
538
+ * @since 4.0.0
539
+ */
540
+ override [Runtime.errorReported] = true
541
+
542
+ /**
543
+ * Returns the explicit user-facing message or a safe fallback from `cause`.
544
+ *
545
+ * @since 4.0.0
546
+ */
547
+ override get message() {
548
+ if (this.userMessage) return this.userMessage
549
+ if (typeof this.cause === "string" && this.cause) return this.cause
550
+ if (this.cause instanceof Error && this.cause.message) return this.cause.message
551
+ return "An error occurred"
552
+ }
527
553
  }
528
554
 
529
555
  /**
@@ -22,6 +22,7 @@ import type * as Path from "../../Path.ts"
22
22
  import * as Predicate from "../../Predicate.ts"
23
23
  import * as References from "../../References.ts"
24
24
  import * as Result from "../../Result.ts"
25
+ import * as Runtime from "../../Runtime.ts"
25
26
  import * as Stdio from "../../Stdio.ts"
26
27
  import * as Terminal from "../../Terminal.ts"
27
28
  import type { Contravariant, Covariant, NoInfer, Simplify } from "../../Types.ts"
@@ -2669,18 +2670,26 @@ const getOutOfScopeGlobalFlagErrors = (
2669
2670
 
2670
2671
  const showHelp = <Name extends string, Input, E, R, ContextInput>(
2671
2672
  command: Command<Name, Input, ContextInput, E, R>,
2672
- error: CliError.ShowHelp
2673
+ error: CliError.ShowHelp,
2674
+ renderErrors: boolean
2673
2675
  ): Effect.Effect<void, CliError.CliError, Environment> =>
2674
2676
  Effect.gen(function*() {
2675
2677
  const { builtIns } = yield* CliConfig.CliConfig
2676
2678
  const formatter = yield* CliOutput.Formatter
2677
2679
  const helpDoc = yield* getHelpForCommandPath(command, error.commandPath, builtIns)
2678
2680
  yield* Console.log(formatter.formatHelpDoc(helpDoc))
2679
- if (error.errors.length > 0) {
2681
+ if (renderErrors && error.errors.length > 0) {
2680
2682
  yield* Console.error(formatter.formatErrors(error.errors as any))
2681
2683
  }
2682
2684
  })
2683
2685
 
2686
+ const showUserError = (error: CliError.UserError): Effect.Effect<void> =>
2687
+ Effect.gen(function*() {
2688
+ const formatter = yield* CliOutput.Formatter
2689
+ yield* Console.error(formatter.formatError(error))
2690
+ error[Runtime.errorReported] = false
2691
+ })
2692
+
2684
2693
  /**
2685
2694
  * Runs a command using the arguments supplied by the `Stdio` service.
2686
2695
  *
@@ -2689,6 +2698,11 @@ const showHelp = <Name extends string, Input, E, R, ContextInput>(
2689
2698
  * Use when command-line arguments should come from `Stdio` at the application
2690
2699
  * entry point.
2691
2700
  *
2701
+ * Help documents are always rendered. By default, parse error details and
2702
+ * `CliError.UserError` failures are also rendered with the installed
2703
+ * `CliOutput.Formatter` before the error is rethrown. Set `renderErrors` to
2704
+ * `false` when the host application owns error rendering.
2705
+ *
2692
2706
  * **Example** (Running commands with standard input)
2693
2707
  *
2694
2708
  * ```ts import.meta.vitest
@@ -2746,6 +2760,11 @@ export const run: {
2746
2760
  * Use when command-line arguments should come from `Stdio` at the application
2747
2761
  * entry point.
2748
2762
  *
2763
+ * Help documents are always rendered. By default, parse error details and
2764
+ * `CliError.UserError` failures are also rendered with the installed
2765
+ * `CliOutput.Formatter` before the error is rethrown. Set `renderErrors` to
2766
+ * `false` when the host application owns error rendering.
2767
+ *
2749
2768
  * **Example** (Running commands with standard input)
2750
2769
  *
2751
2770
  * ```ts import.meta.vitest
@@ -2797,6 +2816,7 @@ export const run: {
2797
2816
  (
2798
2817
  config: {
2799
2818
  readonly version: string
2819
+ readonly renderErrors?: boolean | undefined
2800
2820
  }
2801
2821
  ): <Name extends string, Input, E, R, ContextInput>(
2802
2822
  command: Command<Name, Input, ContextInput, E, R>
@@ -2809,6 +2829,11 @@ export const run: {
2809
2829
  * Use when command-line arguments should come from `Stdio` at the application
2810
2830
  * entry point.
2811
2831
  *
2832
+ * Help documents are always rendered. By default, parse error details and
2833
+ * `CliError.UserError` failures are also rendered with the installed
2834
+ * `CliOutput.Formatter` before the error is rethrown. Set `renderErrors` to
2835
+ * `false` when the host application owns error rendering.
2836
+ *
2812
2837
  * **Example** (Running commands with standard input)
2813
2838
  *
2814
2839
  * ```ts import.meta.vitest
@@ -2861,12 +2886,14 @@ export const run: {
2861
2886
  command: Command<Name, Input, ContextInput, E, R>,
2862
2887
  config: {
2863
2888
  readonly version: string
2889
+ readonly renderErrors?: boolean | undefined
2864
2890
  }
2865
2891
  ): Effect.Effect<void, E | CliError.CliError, R | Environment>
2866
2892
  } = dual(2, <Name extends string, Input, E, R, ContextInput>(
2867
2893
  command: Command<Name, Input, ContextInput, E, R>,
2868
2894
  config: {
2869
2895
  readonly version: string
2896
+ readonly renderErrors?: boolean | undefined
2870
2897
  }
2871
2898
  ) =>
2872
2899
  Stdio.Stdio.use(({ args }) =>
@@ -2884,6 +2911,11 @@ export const run: {
2884
2911
  * Use when you need to test CLI applications or programmatically execute
2885
2912
  * commands with specific arguments.
2886
2913
  *
2914
+ * Help documents are always rendered. By default, parse error details and
2915
+ * `CliError.UserError` failures are also rendered with the installed
2916
+ * `CliOutput.Formatter` before the error is rethrown. Set `renderErrors` to
2917
+ * `false` when the host application owns error rendering.
2918
+ *
2887
2919
  * **Example** (Running commands with explicit arguments)
2888
2920
  *
2889
2921
  * ```ts import.meta.vitest
@@ -2937,6 +2969,7 @@ export const runWith = <const Name extends string, Input, E, R, ContextInput>(
2937
2969
  command: Command<Name, Input, ContextInput, E, R>,
2938
2970
  config: {
2939
2971
  readonly version: string
2972
+ readonly renderErrors?: boolean | undefined
2940
2973
  }
2941
2974
  ): (
2942
2975
  input: ReadonlyArray<string>
@@ -3003,7 +3036,7 @@ export const runWith = <const Name extends string, Input, E, R, ContextInput>(
3003
3036
  }))
3004
3037
  if (shouldRun) {
3005
3038
  yield* Console.log()
3006
- yield* runWith(command, config)(wizardArgs.slice(1))
3039
+ yield* runWith(command, { ...config, renderErrors: false })(wizardArgs.slice(1))
3007
3040
  }
3008
3041
  }).pipe(
3009
3042
  Effect.catchTag("QuitError", () => Console.log(Wizard.renderQuit()))
@@ -3051,7 +3084,14 @@ export const runWith = <const Name extends string, Input, E, R, ContextInput>(
3051
3084
  CliError.isCliError(error) && error._tag === "ShowHelp"
3052
3085
  ? Result.succeed(error)
3053
3086
  : Result.fail(error),
3054
- (error) => Effect.andThen(showHelp(command, error), Effect.fail(error))
3087
+ (error) => Effect.andThen(showHelp(command, error, config.renderErrors !== false), Effect.fail(error))
3088
+ ),
3089
+ Effect.catchFilter(
3090
+ (error) =>
3091
+ config.renderErrors !== false && CliError.isCliError(error) && error._tag === "UserError"
3092
+ ? Result.succeed(error)
3093
+ : Result.fail(error),
3094
+ (error) => Effect.andThen(showUserError(error), Effect.fail(error))
3055
3095
  ),
3056
3096
  Effect.catchFilter(
3057
3097
  (e) =>
@@ -34,7 +34,12 @@ import * as RpcClient from "../rpc/RpcClient.ts"
34
34
  import * as RpcGroup from "../rpc/RpcGroup.ts"
35
35
  import * as RpcSchema from "../rpc/RpcSchema.ts"
36
36
  import * as RpcServer from "../rpc/RpcServer.ts"
37
- import type { AlreadyProcessingMessage, MailboxFull, PersistenceError } from "./ClusterError.ts"
37
+ import type {
38
+ AlreadyProcessingMessage,
39
+ EntityNotAssignedToRunner,
40
+ MailboxFull,
41
+ PersistenceError
42
+ } from "./ClusterError.ts"
38
43
  import { Persisted, ShardGroup, Uninterruptible } from "./ClusterSchema.ts"
39
44
  import { EntityAddress } from "./EntityAddress.ts"
40
45
  import type { EntityId } from "./EntityId.ts"
@@ -117,7 +122,7 @@ export interface Entity<
117
122
  entityId: string
118
123
  ) => RpcClient.RpcClient.From<
119
124
  Rpcs,
120
- MailboxFull | AlreadyProcessingMessage | PersistenceError
125
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
121
126
  >,
122
127
  never,
123
128
  Sharding
@@ -14,7 +14,7 @@ import * as HttpApiEndpoint from "../httpapi/HttpApiEndpoint.ts"
14
14
  import * as HttpApiGroup from "../httpapi/HttpApiGroup.ts"
15
15
  import * as Rpc from "../rpc/Rpc.ts"
16
16
  import * as RpcGroup from "../rpc/RpcGroup.ts"
17
- import { AlreadyProcessingMessage, MailboxFull, PersistenceError } from "./ClusterError.ts"
17
+ import { AlreadyProcessingMessage, EntityNotAssignedToRunner, MailboxFull, PersistenceError } from "./ClusterError.ts"
18
18
  import type * as Entity from "./Entity.ts"
19
19
  import type { EntityId } from "./EntityId.ts"
20
20
 
@@ -24,6 +24,11 @@ const clientErrors = [
24
24
  PersistenceError
25
25
  ] as const
26
26
 
27
+ const requestErrors = [
28
+ ...clientErrors,
29
+ EntityNotAssignedToRunner
30
+ ] as const
31
+
27
32
  /**
28
33
  * Derives an `RpcGroup` from an `Entity`.
29
34
  *
@@ -75,7 +80,7 @@ export const toRpcGroup = <Type extends string, Rpcs extends Rpc.Any>(
75
80
  }
76
81
  const rpc = Rpc.make(`${entity.type}.${parentRpc._tag}`, {
77
82
  payload: payloadSchema,
78
- error: Schema.Union([parentRpc.errorSchema, ...clientErrors]),
83
+ error: Schema.Union([parentRpc.errorSchema, ...requestErrors]),
79
84
  success: parentRpc.successSchema
80
85
  }).annotateMerge(parentRpc.annotations)
81
86
  const rpcDiscard = Rpc.make(`${entity.type}.${parentRpc._tag}Discard`, {
@@ -115,11 +120,12 @@ export type ConvertRpcs<Rpcs extends Rpc.Any, Prefix extends string> = Rpcs exte
115
120
  }>,
116
121
  _Success,
117
122
  Schema.Codec<
118
- _Error["Type"] | MailboxFull | AlreadyProcessingMessage | PersistenceError,
123
+ _Error["Type"] | MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner,
119
124
  | _Error["Encoded"]
120
125
  | typeof MailboxFull["Encoded"]
121
126
  | typeof AlreadyProcessingMessage["Encoded"]
122
- | typeof PersistenceError["Encoded"],
127
+ | typeof PersistenceError["Encoded"]
128
+ | typeof EntityNotAssignedToRunner["Encoded"],
123
129
  _Error["DecodingServices"],
124
130
  _Error["EncodingServices"]
125
131
  >
@@ -193,7 +199,7 @@ export const toHttpApiGroup = <const Name extends string, Type extends string, R
193
199
  params: entityIdPath,
194
200
  payload: parentRpc.payloadSchema,
195
201
  success: parentRpc.successSchema,
196
- error: [parentRpc.errorSchema, ...clientErrors]
202
+ error: [parentRpc.errorSchema, ...requestErrors]
197
203
  }).annotateMerge(parentRpc.annotations)
198
204
  const endpointDiscard = HttpApiEndpoint.post(
199
205
  `${parentRpc._tag}Discard`,
@@ -250,6 +256,7 @@ export type ConvertHttpApi<Rpcs extends Rpc.Any> = Rpcs extends Rpc.Rpc<
250
256
  | typeof MailboxFull
251
257
  | typeof AlreadyProcessingMessage
252
258
  | typeof PersistenceError
259
+ | typeof EntityNotAssignedToRunner
253
260
  >
254
261
  | HttpApiEndpoint.HttpApiEndpoint<
255
262
  `${_Tag}Discard`,
@@ -124,7 +124,7 @@ export class Sharding extends Context.Service<Sharding, {
124
124
  entityId: string
125
125
  ) => RpcClient.RpcClient.From<
126
126
  Rpcs,
127
- MailboxFull | AlreadyProcessingMessage | PersistenceError
127
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
128
128
  >
129
129
  >
130
130
 
@@ -178,7 +178,7 @@ export class Sharding extends Context.Service<Sharding, {
178
178
  discard: boolean
179
179
  ) => Effect.Effect<
180
180
  void,
181
- MailboxFull | AlreadyProcessingMessage | PersistenceError
181
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
182
182
  >
183
183
 
184
184
  /**
@@ -967,18 +967,40 @@ const make = Effect.gen(function*() {
967
967
  retries?: number
968
968
  ): Effect.Effect<
969
969
  void,
970
- MailboxFull | AlreadyProcessingMessage | PersistenceError
970
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
971
971
  > {
972
972
  const isPersisted = Context.get(
973
973
  message._tag === "OutgoingRequest" ? message.annotations : message.rpc.annotations,
974
974
  Persisted
975
975
  )
976
+ const shouldFail = !discard &&
977
+ (message._tag === "OutgoingRequest" || message.envelope._tag === "AckChunk")
978
+ const abandon = (error: EntityNotAssignedToRunner) => {
979
+ if (!isPersisted) {
980
+ return shouldFail
981
+ ? Effect.fail(error)
982
+ : Effect.logDebug("Abandoning outgoing message during shutdown", message.envelope.address)
983
+ }
984
+ const persist = message._tag === "OutgoingRequest"
985
+ ? storage.saveRequest(message)
986
+ : storage.saveEnvelope(message)
987
+ return Effect.catchTag(persist, "MalformedMessage", Effect.die).pipe(
988
+ Effect.andThen(
989
+ shouldFail
990
+ ? Effect.fail(error)
991
+ : Effect.logWarning("Persisting outgoing message abandoned during shutdown", message.envelope.address)
992
+ )
993
+ )
994
+ }
976
995
  return Effect.catchFilter(
977
996
  Effect.suspend(() => {
978
997
  const address = message.envelope.address
979
998
  if (isPersisted && !storageEnabled) {
980
999
  return Effect.die("Sharding.sendOutgoing: Persisted messages require MessageStorage")
981
1000
  }
1001
+ if (shouldFail && MutableRef.get(isShutdown)) {
1002
+ return Effect.fail(new EntityNotAssignedToRunner({ address }))
1003
+ }
982
1004
  const maybeRunner = MutableHashMap.get(shardAssignments, address.shardId)
983
1005
  const runnerIsLocal = Option.isSome(maybeRunner) && isLocalRunner(maybeRunner.value)
984
1006
  if (isPersisted) {
@@ -1003,17 +1025,7 @@ const make = Effect.gen(function*() {
1003
1025
  const cannotRecover = MutableRef.get(isShutdown) ||
1004
1026
  (targetManager !== undefined && targetManager.status !== "alive")
1005
1027
  if (cannotRecover) {
1006
- if (!isPersisted) {
1007
- return Effect.logDebug("Abandoning outgoing message during shutdown", message.envelope.address)
1008
- }
1009
- const persist = message._tag === "OutgoingRequest"
1010
- ? storage.saveRequest(message)
1011
- : storage.saveEnvelope(message)
1012
- return Effect.catchTag(persist, "MalformedMessage", Effect.die).pipe(
1013
- Effect.andThen(
1014
- Effect.logWarning("Persisting outgoing message abandoned during shutdown", message.envelope.address)
1015
- )
1016
- )
1028
+ return abandon(error)
1017
1029
  }
1018
1030
  }
1019
1031
  if (retries === 0) {
@@ -1183,7 +1195,7 @@ const make = Effect.gen(function*() {
1183
1195
  Entity<any, any>,
1184
1196
  (entityId: string) => RpcClient.RpcClient<
1185
1197
  any,
1186
- MailboxFull | AlreadyProcessingMessage
1198
+ MailboxFull | AlreadyProcessingMessage | EntityNotAssignedToRunner
1187
1199
  >,
1188
1200
  never
1189
1201
  > = yield* ResourceMap.make(
@@ -1196,7 +1208,7 @@ const make = Effect.gen(function*() {
1196
1208
  flatten: true,
1197
1209
  onFromClient(options): Effect.Effect<
1198
1210
  void,
1199
- MailboxFull | AlreadyProcessingMessage | PersistenceError
1211
+ MailboxFull | AlreadyProcessingMessage | PersistenceError | EntityNotAssignedToRunner
1200
1212
  > {
1201
1213
  const address = Context.getUnsafe(options.context, ClientAddressTag)
1202
1214
  switch (options.message._tag) {
@@ -1337,7 +1349,7 @@ const make = Effect.gen(function*() {
1337
1349
  const makeClient = <Type extends string, Rpcs extends Rpc.Any>(entity: Entity<Type, Rpcs>): Effect.Effect<
1338
1350
  (
1339
1351
  entityId: string
1340
- ) => RpcClient.RpcClient.From<Rpcs, MailboxFull | AlreadyProcessingMessage>
1352
+ ) => RpcClient.RpcClient.From<Rpcs, MailboxFull | AlreadyProcessingMessage | EntityNotAssignedToRunner>
1341
1353
  > => clients.get(entity) as any
1342
1354
 
1343
1355
  const clientRespondDiscard = (_reply: Reply.Reply<any>) => Effect.void
@@ -19,14 +19,15 @@ import type { Identity } from "./EventLog.ts"
19
19
  import { makeGetIdentityRootSecretMaterial } from "./internal/identityRootSecretDerivation.ts"
20
20
 
21
21
  /**
22
- * Schema for an encrypted journal entry paired with the id of the original
23
- * entry.
22
+ * Schema for an encrypted journal entry paired with its initialization vector
23
+ * and the id of the original entry.
24
24
  *
25
25
  * @category models
26
26
  * @since 4.0.0
27
27
  */
28
28
  export const EncryptedEntry = Schema.Struct({
29
29
  entryId: EntryId,
30
+ iv: Transferable.Uint8Array,
30
31
  encryptedEntry: Transferable.Uint8Array
31
32
  })
32
33
 
@@ -76,10 +77,12 @@ export class EventLogEncryption extends Context.Service<EventLogEncryption, {
76
77
  readonly encrypt: (
77
78
  identity: Identity["Service"],
78
79
  entries: ReadonlyArray<Entry>
79
- ) => Effect.Effect<{
80
- readonly iv: Uint8Array<ArrayBuffer>
81
- readonly encryptedEntries: ReadonlyArray<Uint8Array<ArrayBuffer>>
82
- }>
80
+ ) => Effect.Effect<
81
+ ReadonlyArray<{
82
+ readonly iv: Uint8Array<ArrayBuffer>
83
+ readonly encryptedEntry: Uint8Array<ArrayBuffer>
84
+ }>
85
+ >
83
86
  readonly decrypt: (
84
87
  identity: Identity["Service"],
85
88
  entries: ReadonlyArray<EncryptedRemoteEntry>
@@ -104,22 +107,18 @@ export const makeEncryptionSubtle = (crypto: Crypto): Effect.Effect<EventLogEncr
104
107
  encrypt: Effect.fnUntraced(function*(identity, entries) {
105
108
  const data = yield* Effect.orDie(Entry.encodeArray(entries))
106
109
  const key = (yield* getIdentityRootSecretMaterial(identity)).encryptionKey
107
- const iv = crypto.getRandomValues(new Uint8Array(12))
108
- const encryptedEntries = yield* Effect.promise(() =>
110
+ return yield* Effect.promise(() =>
109
111
  Promise.all(
110
- data.map((entry) =>
111
- crypto.subtle.encrypt(
112
+ data.map((entry) => {
113
+ const iv = crypto.getRandomValues(new Uint8Array(12))
114
+ return crypto.subtle.encrypt(
112
115
  { name: "AES-GCM", iv: toBufferSource(iv), tagLength: 128 },
113
116
  key,
114
117
  toBufferSource(entry)
115
- )
116
- )
118
+ ).then((encryptedEntry) => ({ iv, encryptedEntry: new Uint8Array(encryptedEntry) }))
119
+ })
117
120
  )
118
121
  )
119
- return {
120
- iv,
121
- encryptedEntries: encryptedEntries.map((entry) => new Uint8Array(entry))
122
- }
123
122
  }),
124
123
  decrypt: Effect.fnUntraced(function*(identity, entries) {
125
124
  const key = (yield* getIdentityRootSecretMaterial(identity)).encryptionKey