effect 4.0.0-beta.40 → 4.0.0-beta.42

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/BigDecimal.d.ts +36 -0
  2. package/dist/BigDecimal.d.ts.map +1 -1
  3. package/dist/BigDecimal.js +52 -0
  4. package/dist/BigDecimal.js.map +1 -1
  5. package/dist/Effect.d.ts +20 -63
  6. package/dist/Effect.d.ts.map +1 -1
  7. package/dist/Effect.js +24 -65
  8. package/dist/Effect.js.map +1 -1
  9. package/dist/Layer.js +1 -1
  10. package/dist/Layer.js.map +1 -1
  11. package/dist/ManagedRuntime.d.ts.map +1 -1
  12. package/dist/ManagedRuntime.js +19 -7
  13. package/dist/ManagedRuntime.js.map +1 -1
  14. package/dist/MutableList.d.ts.map +1 -1
  15. package/dist/MutableList.js +3 -0
  16. package/dist/MutableList.js.map +1 -1
  17. package/dist/Number.d.ts.map +1 -1
  18. package/dist/Number.js +12 -3
  19. package/dist/Number.js.map +1 -1
  20. package/dist/SchemaRepresentation.d.ts.map +1 -1
  21. package/dist/SchemaRepresentation.js +16 -14
  22. package/dist/SchemaRepresentation.js.map +1 -1
  23. package/dist/ServiceMap.d.ts +14 -26
  24. package/dist/ServiceMap.d.ts.map +1 -1
  25. package/dist/ServiceMap.js +6 -5
  26. package/dist/ServiceMap.js.map +1 -1
  27. package/dist/TxChunk.d.ts +39 -39
  28. package/dist/TxChunk.d.ts.map +1 -1
  29. package/dist/TxChunk.js +3 -3
  30. package/dist/TxChunk.js.map +1 -1
  31. package/dist/TxDeferred.d.ts +9 -9
  32. package/dist/TxDeferred.d.ts.map +1 -1
  33. package/dist/TxDeferred.js +2 -2
  34. package/dist/TxDeferred.js.map +1 -1
  35. package/dist/TxHashMap.d.ts +59 -59
  36. package/dist/TxHashMap.d.ts.map +1 -1
  37. package/dist/TxHashMap.js +8 -8
  38. package/dist/TxHashMap.js.map +1 -1
  39. package/dist/TxHashSet.d.ts +35 -35
  40. package/dist/TxHashSet.d.ts.map +1 -1
  41. package/dist/TxHashSet.js +7 -7
  42. package/dist/TxHashSet.js.map +1 -1
  43. package/dist/TxPriorityQueue.d.ts +23 -23
  44. package/dist/TxPriorityQueue.d.ts.map +1 -1
  45. package/dist/TxPriorityQueue.js +4 -4
  46. package/dist/TxPriorityQueue.js.map +1 -1
  47. package/dist/TxPubSub.d.ts +14 -14
  48. package/dist/TxPubSub.d.ts.map +1 -1
  49. package/dist/TxPubSub.js +12 -12
  50. package/dist/TxPubSub.js.map +1 -1
  51. package/dist/TxQueue.d.ts +33 -33
  52. package/dist/TxQueue.d.ts.map +1 -1
  53. package/dist/TxQueue.js +29 -44
  54. package/dist/TxQueue.js.map +1 -1
  55. package/dist/TxReentrantLock.d.ts +10 -33
  56. package/dist/TxReentrantLock.d.ts.map +1 -1
  57. package/dist/TxReentrantLock.js +14 -37
  58. package/dist/TxReentrantLock.js.map +1 -1
  59. package/dist/TxRef.d.ts +36 -42
  60. package/dist/TxRef.d.ts.map +1 -1
  61. package/dist/TxRef.js +16 -25
  62. package/dist/TxRef.js.map +1 -1
  63. package/dist/TxSemaphore.d.ts +8 -8
  64. package/dist/TxSemaphore.d.ts.map +1 -1
  65. package/dist/TxSemaphore.js +10 -10
  66. package/dist/TxSemaphore.js.map +1 -1
  67. package/dist/TxSubscriptionRef.d.ts +14 -14
  68. package/dist/TxSubscriptionRef.d.ts.map +1 -1
  69. package/dist/TxSubscriptionRef.js +5 -5
  70. package/dist/TxSubscriptionRef.js.map +1 -1
  71. package/dist/index.d.ts +6 -6
  72. package/dist/index.js +6 -6
  73. package/dist/internal/effect.js +3 -0
  74. package/dist/internal/effect.js.map +1 -1
  75. package/dist/internal/schema/representation.js +28 -0
  76. package/dist/internal/schema/representation.js.map +1 -1
  77. package/dist/unstable/ai/Chat.d.ts +31 -2
  78. package/dist/unstable/ai/Chat.d.ts.map +1 -1
  79. package/dist/unstable/ai/Chat.js.map +1 -1
  80. package/dist/unstable/ai/LanguageModel.d.ts +68 -6
  81. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  82. package/dist/unstable/ai/LanguageModel.js +88 -11
  83. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  84. package/dist/unstable/ai/OpenAiStructuredOutput.js +3 -0
  85. package/dist/unstable/ai/OpenAiStructuredOutput.js.map +1 -1
  86. package/dist/unstable/cli/Prompt.js +1 -1
  87. package/dist/unstable/cli/Prompt.js.map +1 -1
  88. package/dist/unstable/cli/internal/command.d.ts.map +1 -1
  89. package/dist/unstable/cli/internal/command.js +12 -1
  90. package/dist/unstable/cli/internal/command.js.map +1 -1
  91. package/dist/unstable/cluster/ClusterSchema.d.ts +18 -0
  92. package/dist/unstable/cluster/ClusterSchema.d.ts.map +1 -1
  93. package/dist/unstable/cluster/ClusterSchema.js +21 -1
  94. package/dist/unstable/cluster/ClusterSchema.js.map +1 -1
  95. package/dist/unstable/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  96. package/dist/unstable/cluster/ClusterWorkflowEngine.js +6 -3
  97. package/dist/unstable/cluster/ClusterWorkflowEngine.js.map +1 -1
  98. package/dist/unstable/cluster/Entity.d.ts.map +1 -1
  99. package/dist/unstable/cluster/Entity.js +1 -0
  100. package/dist/unstable/cluster/Entity.js.map +1 -1
  101. package/dist/unstable/cluster/Message.d.ts +4 -2
  102. package/dist/unstable/cluster/Message.d.ts.map +1 -1
  103. package/dist/unstable/cluster/Message.js +13 -6
  104. package/dist/unstable/cluster/Message.js.map +1 -1
  105. package/dist/unstable/cluster/MessageStorage.d.ts +23 -0
  106. package/dist/unstable/cluster/MessageStorage.d.ts.map +1 -1
  107. package/dist/unstable/cluster/MessageStorage.js +22 -7
  108. package/dist/unstable/cluster/MessageStorage.js.map +1 -1
  109. package/dist/unstable/cluster/Sharding.d.ts.map +1 -1
  110. package/dist/unstable/cluster/Sharding.js +24 -20
  111. package/dist/unstable/cluster/Sharding.js.map +1 -1
  112. package/dist/unstable/cluster/SqlMessageStorage.d.ts.map +1 -1
  113. package/dist/unstable/cluster/SqlMessageStorage.js +3 -1
  114. package/dist/unstable/cluster/SqlMessageStorage.js.map +1 -1
  115. package/dist/unstable/cluster/internal/entityManager.js +10 -4
  116. package/dist/unstable/cluster/internal/entityManager.js.map +1 -1
  117. package/dist/unstable/http/index.d.ts +2 -2
  118. package/dist/unstable/http/index.d.ts.map +1 -1
  119. package/dist/unstable/http/index.js +2 -2
  120. package/dist/unstable/http/index.js.map +1 -1
  121. package/dist/unstable/httpapi/HttpApiSchema.d.ts +1 -3
  122. package/dist/unstable/httpapi/HttpApiSchema.d.ts.map +1 -1
  123. package/dist/unstable/httpapi/OpenApi.d.ts +1 -0
  124. package/dist/unstable/httpapi/OpenApi.d.ts.map +1 -1
  125. package/dist/unstable/rpc/Rpc.d.ts +1 -0
  126. package/dist/unstable/rpc/Rpc.d.ts.map +1 -1
  127. package/dist/unstable/rpc/Rpc.js.map +1 -1
  128. package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
  129. package/dist/unstable/rpc/RpcClient.js +43 -26
  130. package/dist/unstable/rpc/RpcClient.js.map +1 -1
  131. package/dist/unstable/socket/Socket.d.ts +1 -1
  132. package/dist/unstable/socket/Socket.d.ts.map +1 -1
  133. package/dist/unstable/socket/Socket.js +1 -1
  134. package/dist/unstable/socket/Socket.js.map +1 -1
  135. package/dist/unstable/workflow/Activity.d.ts +5 -0
  136. package/dist/unstable/workflow/Activity.d.ts.map +1 -1
  137. package/dist/unstable/workflow/Activity.js +13 -0
  138. package/dist/unstable/workflow/Activity.js.map +1 -1
  139. package/package.json +1 -1
  140. package/src/BigDecimal.ts +54 -0
  141. package/src/Effect.ts +25 -75
  142. package/src/Layer.ts +1 -1
  143. package/src/ManagedRuntime.ts +33 -20
  144. package/src/MutableList.ts +3 -0
  145. package/src/Number.ts +13 -3
  146. package/src/SchemaRepresentation.ts +8 -6
  147. package/src/ServiceMap.ts +16 -31
  148. package/src/TxChunk.ts +53 -62
  149. package/src/TxDeferred.ts +14 -17
  150. package/src/TxHashMap.ts +91 -101
  151. package/src/TxHashSet.ts +68 -70
  152. package/src/TxPriorityQueue.ts +34 -38
  153. package/src/TxPubSub.ts +30 -32
  154. package/src/TxQueue.ts +70 -84
  155. package/src/TxReentrantLock.ts +30 -54
  156. package/src/TxRef.ts +53 -65
  157. package/src/TxSemaphore.ts +23 -24
  158. package/src/TxSubscriptionRef.ts +25 -27
  159. package/src/index.ts +6 -6
  160. package/src/internal/effect.ts +3 -0
  161. package/src/internal/schema/representation.ts +28 -0
  162. package/src/unstable/ai/Chat.ts +79 -18
  163. package/src/unstable/ai/LanguageModel.ts +182 -66
  164. package/src/unstable/ai/OpenAiStructuredOutput.ts +3 -0
  165. package/src/unstable/cli/Prompt.ts +3 -1
  166. package/src/unstable/cli/internal/command.ts +16 -1
  167. package/src/unstable/cluster/ClusterSchema.ts +29 -1
  168. package/src/unstable/cluster/ClusterWorkflowEngine.ts +22 -3
  169. package/src/unstable/cluster/Entity.ts +1 -0
  170. package/src/unstable/cluster/Message.ts +22 -10
  171. package/src/unstable/cluster/MessageStorage.ts +37 -6
  172. package/src/unstable/cluster/Sharding.ts +31 -23
  173. package/src/unstable/cluster/SqlMessageStorage.ts +6 -1
  174. package/src/unstable/cluster/internal/entityManager.ts +14 -5
  175. package/src/unstable/http/index.ts +2 -2
  176. package/src/unstable/httpapi/HttpApiSchema.ts +1 -1
  177. package/src/unstable/httpapi/OpenApi.ts +1 -0
  178. package/src/unstable/rpc/Rpc.ts +1 -0
  179. package/src/unstable/rpc/RpcClient.ts +45 -33
  180. package/src/unstable/socket/Socket.ts +1 -1
  181. package/src/unstable/workflow/Activity.ts +23 -0
@@ -6,6 +6,7 @@ import { Clock } from "../../Clock.ts"
6
6
  import * as Data from "../../Data.ts"
7
7
  import * as Effect from "../../Effect.ts"
8
8
  import * as Exit from "../../Exit.ts"
9
+ import { constFalse, identity } from "../../Function.ts"
9
10
  import * as Latch from "../../Latch.ts"
10
11
  import * as Layer from "../../Layer.ts"
11
12
  import * as Option from "../../Option.ts"
@@ -140,6 +141,13 @@ export class MessageStorage extends ServiceMap.Service<MessageStorage, {
140
141
  readonly clearAddress: (
141
142
  address: EntityAddress
142
143
  ) => Effect.Effect<void, PersistenceError>
144
+
145
+ /**
146
+ * Used to wrap requests with transactions.
147
+ */
148
+ readonly withTransaction: <A, E, R>(
149
+ effect: Effect.Effect<A, E, R>
150
+ ) => Effect.Effect<A, E, R>
143
151
  }>()("effect/cluster/MessageStorage") {}
144
152
 
145
153
  /**
@@ -315,6 +323,13 @@ export type Encoded = {
315
323
  readonly resetShards: (
316
324
  shardIds: Arr.NonEmptyArray<string>
317
325
  ) => Effect.Effect<void, PersistenceError>
326
+
327
+ /**
328
+ * Used to wrap requests with transactions.
329
+ */
330
+ readonly withTransaction: <A, E, R>(
331
+ effect: Effect.Effect<A, E, R>
332
+ ) => Effect.Effect<A, E, R>
318
333
  }
319
334
 
320
335
  /**
@@ -517,20 +532,22 @@ export const makeEncoded: (encoded: Encoded) => Effect.Effect<
517
532
  const primaryKey = Envelope.primaryKeyByAddress(options)
518
533
  return encoded.requestIdForPrimaryKey(primaryKey)
519
534
  },
520
- unprocessedMessages: (shardIds) => {
535
+ unprocessedMessages(shardIds) {
536
+ const storage = this as MessageStorage["Service"]
521
537
  const shards = Array.from(shardIds, (id) => id.toString())
522
538
  if (!Arr.isArrayNonEmpty(shards)) return Effect.succeed([])
523
539
  return Effect.flatMap(
524
540
  Effect.suspend(() => encoded.unprocessedMessages(shards, clock.currentTimeMillisUnsafe())),
525
- decodeMessages
541
+ (messages) => decodeMessages(storage, messages)
526
542
  )
527
543
  },
528
544
  unprocessedMessagesById(messageIds) {
545
+ const storage = this as MessageStorage["Service"]
529
546
  const ids = Array.from(messageIds)
530
547
  if (!Arr.isArrayNonEmpty(ids)) return Effect.succeed([])
531
548
  return Effect.flatMap(
532
549
  Effect.suspend(() => encoded.unprocessedMessagesById(ids, clock.currentTimeMillisUnsafe())),
533
- decodeMessages
550
+ (messages) => decodeMessages(storage, messages)
534
551
  )
535
552
  },
536
553
  resetAddress: encoded.resetAddress,
@@ -539,10 +556,12 @@ export const makeEncoded: (encoded: Encoded) => Effect.Effect<
539
556
  const shards = Array.from(shardIds, (id) => id.toString())
540
557
  if (!Arr.isArrayNonEmpty(shards)) return Effect.void
541
558
  return encoded.resetShards(shards)
542
- }
559
+ },
560
+ withTransaction: encoded.withTransaction
543
561
  })
544
562
 
545
563
  const decodeMessages = (
564
+ storage: MessageStorage["Service"],
546
565
  envelopes: Array<{
547
566
  readonly envelope: Envelope.Encoded
548
567
  readonly lastSentReply: Option.Option<Reply.Encoded>
@@ -659,7 +678,8 @@ export const noop: MessageStorage["Service"] = Effect.runSync(make({
659
678
  unprocessedMessagesById: () => Effect.succeed([]),
660
679
  resetAddress: () => Effect.void,
661
680
  clearAddress: () => Effect.void,
662
- resetShards: () => Effect.void
681
+ resetShards: () => Effect.void,
682
+ withTransaction: identity
663
683
  }))
664
684
 
665
685
  /**
@@ -673,6 +693,16 @@ export type MemoryEntry = {
673
693
  deliverAt: number | null
674
694
  }
675
695
 
696
+ /**
697
+ * Can be used in tests to simulate a transaction.
698
+ *
699
+ * @since 4.0.0
700
+ * @category Memory
701
+ */
702
+ export const MemoryTransaction = ServiceMap.Reference<boolean>("effect/cluster/MessageStorage/MemoryTransaction", {
703
+ defaultValue: constFalse
704
+ })
705
+
676
706
  /**
677
707
  * @since 4.0.0
678
708
  * @category Memory
@@ -859,7 +889,8 @@ export class MemoryDriver extends ServiceMap.Service<MemoryDriver>()("effect/clu
859
889
  journal.splice(i, 1)
860
890
  }
861
891
  }),
862
- resetShards: () => Effect.void
892
+ resetShards: () => Effect.void,
893
+ withTransaction: Effect.provideService(MemoryTransaction, true)
863
894
  }
864
895
 
865
896
  const storage = yield* makeEncoded(encoded)
@@ -820,7 +820,10 @@ const make = Effect.gen(function*() {
820
820
  return Effect.catchFilter(
821
821
  Effect.suspend(() => {
822
822
  const address = message.envelope.address
823
- const isPersisted = ServiceMap.get(message.rpc.annotations, Persisted)
823
+ const isPersisted = ServiceMap.get(
824
+ message._tag === "OutgoingRequest" ? message.annotations : message.rpc.annotations,
825
+ Persisted
826
+ )
824
827
  if (isPersisted && !storageEnabled) {
825
828
  return Effect.die("Sharding.sendOutgoing: Persisted messages require MessageStorage")
826
829
  }
@@ -1000,6 +1003,7 @@ const make = Effect.gen(function*() {
1000
1003
  type ClientRequestEntry = {
1001
1004
  readonly rpc: Rpc.AnyWithProps
1002
1005
  readonly services: ServiceMap.ServiceMap<never>
1006
+ readonly message: Message.OutgoingRequest<any>
1003
1007
  lastChunkId?: Snowflake.Snowflake
1004
1008
  }
1005
1009
  const clientRequests = new Map<Snowflake.Snowflake, ClientRequestEntry>()
@@ -1029,35 +1033,39 @@ const make = Effect.gen(function*() {
1029
1033
  const id = Snowflake.Snowflake(options.message.id)
1030
1034
  const rpc = entity.protocol.requests.get(options.message.tag)!
1031
1035
  let respond: (reply: Reply.Reply<any>) => Effect.Effect<void>
1036
+ const envelope = Envelope.makeRequest<any>({
1037
+ requestId: id,
1038
+ address,
1039
+ tag: options.message.tag,
1040
+ payload: options.message.payload,
1041
+ headers: options.message.headers,
1042
+ traceId: options.message.traceId,
1043
+ spanId: options.message.spanId,
1044
+ sampled: options.message.sampled
1045
+ })
1046
+ const message = new Message.OutgoingRequest({
1047
+ envelope,
1048
+ lastReceivedReply: Option.none(),
1049
+ rpc,
1050
+ services: fiber.services as ServiceMap.ServiceMap<any>,
1051
+ respond: (reply) => respond(reply),
1052
+ annotations: ServiceMap.get(rpc.annotations, ClusterSchema.Dynamic)(
1053
+ rpc.annotations,
1054
+ envelope as any
1055
+ )
1056
+ })
1032
1057
  if (!options.discard) {
1033
1058
  const entry: ClientRequestEntry = {
1034
1059
  rpc: rpc as any,
1035
- services: fiber.currentContext
1060
+ services: fiber.currentContext,
1061
+ message
1036
1062
  }
1037
1063
  clientRequests.set(id, entry)
1038
1064
  respond = makeClientRespond(entry, client.write)
1039
1065
  } else {
1040
1066
  respond = clientRespondDiscard
1041
1067
  }
1042
- return sendOutgoing(
1043
- new Message.OutgoingRequest({
1044
- envelope: Envelope.makeRequest({
1045
- requestId: id,
1046
- address,
1047
- tag: options.message.tag,
1048
- payload: options.message.payload,
1049
- headers: options.message.headers,
1050
- traceId: options.message.traceId,
1051
- spanId: options.message.spanId,
1052
- sampled: options.message.sampled
1053
- }),
1054
- lastReceivedReply: Option.none(),
1055
- rpc,
1056
- services: fiber.services as ServiceMap.ServiceMap<any>,
1057
- respond
1058
- }),
1059
- options.discard
1060
- )
1068
+ return sendOutgoing(message, options.discard)
1061
1069
  }
1062
1070
  case "Ack": {
1063
1071
  const requestId = Snowflake.Snowflake(options.message.requestId)
@@ -1081,14 +1089,14 @@ const make = Effect.gen(function*() {
1081
1089
  const entry = clientRequests.get(requestId)!
1082
1090
  if (!entry) return Effect.void
1083
1091
  clientRequests.delete(requestId)
1084
- if (ClusterSchema.isUninterruptibleForClient(entry.rpc.annotations)) {
1092
+ if (ClusterSchema.isUninterruptibleForClient(entry.message.annotations)) {
1085
1093
  return Effect.void
1086
1094
  }
1087
1095
  // for durable messages, we ignore interrupts on shutdown or as a
1088
1096
  // result of a shard being resassigned
1089
1097
  const isTransientInterrupt = MutableRef.get(isShutdown) ||
1090
1098
  options.message.interruptors.some((id) => internalInterruptors.has(id))
1091
- if (isTransientInterrupt && ServiceMap.get(entry.rpc.annotations, Persisted)) {
1099
+ if (isTransientInterrupt && ServiceMap.get(entry.message.annotations, Persisted)) {
1092
1100
  return Effect.void
1093
1101
  }
1094
1102
  return Effect.ignore(sendOutgoing(
@@ -9,7 +9,7 @@ import * as Schedule from "../../Schedule.ts"
9
9
  import * as Migrator from "../sql/Migrator.ts"
10
10
  import * as SqlClient from "../sql/SqlClient.ts"
11
11
  import type { Row } from "../sql/SqlConnection.ts"
12
- import type { SqlError } from "../sql/SqlError.ts"
12
+ import { isSqlError, type SqlError } from "../sql/SqlError.ts"
13
13
  import { PersistenceError } from "./ClusterError.ts"
14
14
  import type * as Envelope from "./Envelope.ts"
15
15
  import * as MessageStorage from "./MessageStorage.ts"
@@ -592,6 +592,11 @@ export const make: (options?: {
592
592
  Effect.asVoid,
593
593
  PersistenceError.refail,
594
594
  withTracerDisabled
595
+ ),
596
+
597
+ withTransaction: (effect) =>
598
+ sql.withTransaction(effect).pipe(
599
+ Effect.catchIf(isSqlError, Effect.die)
595
600
  )
596
601
  })
597
602
  }, withTracerDisabled)
@@ -22,7 +22,8 @@ import { RequestId } from "../../rpc/RpcMessage.ts"
22
22
  import * as RpcServer from "../../rpc/RpcServer.ts"
23
23
  import { AlreadyProcessingMessage, EntityNotAssignedToRunner, MailboxFull, MalformedMessage } from "../ClusterError.ts"
24
24
  import * as ClusterMetrics from "../ClusterMetrics.ts"
25
- import { isUninterruptibleForServer, Persisted } from "../ClusterSchema.ts"
25
+ import { isUninterruptibleForServer, Persisted, WithTransaction } from "../ClusterSchema.ts"
26
+ import * as ClusterSchema from "../ClusterSchema.ts"
26
27
  import type { Entity, HandlersFrom } from "../Entity.ts"
27
28
  import { CurrentAddress, CurrentRunnerAddress, KeepAliveLatch, KeepAliveRpc, Request } from "../Entity.ts"
28
29
  import type { EntityAddress } from "../EntityAddress.ts"
@@ -187,9 +188,9 @@ export const make = Effect.fnUntraced(function*<
187
188
  // interrupt.
188
189
  if (
189
190
  storageEnabled &&
190
- ServiceMap.get(request.rpc.annotations, Persisted) &&
191
+ ServiceMap.get(request.message.annotations, Persisted) &&
191
192
  Exit.hasInterrupts(response.exit) &&
192
- (isShuttingDown || isUninterruptibleForServer(request.rpc.annotations))
193
+ (isShuttingDown || isUninterruptibleForServer(request.message.annotations))
193
194
  ) {
194
195
  if (!isShuttingDown) {
195
196
  return server.write(0, {
@@ -395,7 +396,7 @@ export const make = Effect.fnUntraced(function*<
395
396
  }
396
397
 
397
398
  const rpc = entityRpcs.get(message.envelope.tag)! as any as Rpc.AnyWithProps
398
- if (!storageEnabled && ServiceMap.get(rpc.annotations, Persisted)) {
399
+ if (!storageEnabled && ServiceMap.get(message.annotations, Persisted)) {
399
400
  return Effect.die(
400
401
  "EntityManager.sendLocal: Cannot process a persisted message without MessageStorage"
401
402
  )
@@ -448,7 +449,7 @@ export const make = Effect.fnUntraced(function*<
448
449
  })
449
450
  }
450
451
  server.activeRequests.set(message.envelope.requestId, entry)
451
- return server.write(0, {
452
+ let write = server.write(0, {
452
453
  ...message.envelope,
453
454
  id: RequestId(message.envelope.requestId),
454
455
  payload: new Request({
@@ -459,6 +460,10 @@ export const make = Effect.fnUntraced(function*<
459
460
  )
460
461
  })
461
462
  })
463
+ if (ServiceMap.get(message.annotations, WithTransaction)) {
464
+ write = options.storage.withTransaction(write)
465
+ }
466
+ return write
462
467
  }
463
468
  case "IncomingEnvelope": {
464
469
  const entry = server.activeRequests.get(message.envelope.requestId)
@@ -558,6 +563,10 @@ export const make = Effect.fnUntraced(function*<
558
563
  const rpc = entityRpcs.get(decoded.envelope.tag)!
559
564
  return sendLocal(
560
565
  new Message.IncomingRequestLocal({
566
+ annotations: ServiceMap.get(rpc.annotations, ClusterSchema.Dynamic)(
567
+ rpc.annotations,
568
+ decoded.envelope as any
569
+ ),
561
570
  envelope: decoded.envelope,
562
571
  lastSentReply: decoded.lastSentReply,
563
572
  respond: (reply) =>
@@ -137,9 +137,9 @@ export * as Template from "./Template.ts"
137
137
  /**
138
138
  * @since 4.0.0
139
139
  */
140
- export * as UrlParams from "./UrlParams.ts"
140
+ export * as Url from "./Url.ts"
141
141
 
142
142
  /**
143
143
  * @since 4.0.0
144
144
  */
145
- export * as Url from "./Url.ts"
145
+ export * as UrlParams from "./UrlParams.ts"
@@ -299,7 +299,7 @@ export function asJson(options?: {
299
299
  export function asFormUrlEncoded(options?: {
300
300
  readonly contentType?: string
301
301
  }) {
302
- return <S extends Schema.Top & { readonly Encoded: Record<string, string | ReadonlyArray<string> | undefined> }>(
302
+ return <S extends Schema.Top>(
303
303
  self: S
304
304
  ) => asNonMultipartEncoding(self, { _tag: "FormUrlEncoded", ...options })
305
305
  }
@@ -745,6 +745,7 @@ export interface OpenAPISpecExternalDocs {
745
745
  export interface OpenAPISpecLicense {
746
746
  name: string
747
747
  url?: string
748
+ [key: string]: unknown
748
749
  }
749
750
 
750
751
  /**
@@ -171,6 +171,7 @@ export interface Any extends Pipeable {
171
171
  readonly [TypeId]: typeof TypeId
172
172
  readonly _tag: string
173
173
  readonly key: string
174
+ readonly annotations: ServiceMap.ServiceMap<never>
174
175
  }
175
176
 
176
177
  /**
@@ -825,10 +825,20 @@ export const makeProtocolHttp = (client: HttpClient.HttpClient): Effect.Effect<
825
825
  Protocol.make(Effect.fnUntraced(function*(writeResponse) {
826
826
  const serialization = yield* RpcSerialization.RpcSerialization
827
827
  const isFramed = serialization.includesFraming
828
+ const httpClientError = (cause: any) =>
829
+ new RpcClientError({
830
+ reason: HttpClientErrorSchema.fromHttpClientError(cause)
831
+ })
832
+ const protocolDefect = (message: string, cause: unknown) =>
833
+ new RpcClientError({
834
+ reason: new RpcClientDefect({ message, cause })
835
+ })
836
+ const emptyResponseError = (request: FromClientEncoded) =>
837
+ protocolDefect("Received empty HTTP response from RPC server", request)
828
838
 
829
- const send = (request: FromClientEncoded): Effect.Effect<void, RpcClientError> => {
839
+ const send = Effect.fnUntraced(function*(request: FromClientEncoded) {
830
840
  if (request._tag !== "Request") {
831
- return Effect.void
841
+ return
832
842
  }
833
843
 
834
844
  const parser = serialization.makeUnsafe()
@@ -838,34 +848,37 @@ export const makeProtocolHttp = (client: HttpClient.HttpClient): Effect.Effect<
838
848
  HttpBody.text(encoded, serialization.contentType) :
839
849
  HttpBody.uint8Array(encoded, serialization.contentType)
840
850
 
851
+ const response = yield* client.post("", { body }).pipe(Effect.mapError(httpClientError))
852
+
841
853
  if (!isFramed) {
842
- return client.post("", { body }).pipe(
843
- Effect.flatMap((r) => r.text),
844
- Effect.mapError((cause) =>
845
- new RpcClientError({
846
- reason: HttpClientErrorSchema.fromHttpClientError(cause)
847
- })
848
- ),
849
- Effect.flatMap((text) => {
850
- const u = parser.decode(text)
851
- if (!Array.isArray(u)) {
852
- return Effect.die(`Expected an array of responses, but got: ${u}`)
853
- }
854
- let i = 0
855
- return Effect.whileLoop({
856
- while: () => i < u.length,
857
- body: () => writeResponse(u[i++]),
858
- step: constVoid
859
- })
860
- })
861
- )
854
+ const text = yield* response.text.pipe(Effect.mapError(httpClientError))
855
+ const responses = yield* Effect.try({
856
+ try: () => parser.decode(text),
857
+ catch: (cause) => protocolDefect("Error decoding HTTP response", cause)
858
+ })
859
+ if (!Array.isArray(responses)) {
860
+ return yield* Effect.fail(protocolDefect("Expected an array of responses", responses))
861
+ }
862
+ if (responses.length === 0) {
863
+ return yield* Effect.fail(emptyResponseError(request))
864
+ }
865
+ let i = 0
866
+ return yield* Effect.whileLoop({
867
+ while: () => i < responses.length,
868
+ body: () => writeResponse(responses[i++]),
869
+ step: constVoid
870
+ })
862
871
  }
863
872
 
864
- return client.post("", { body }).pipe(
865
- Effect.flatMap((r) =>
866
- Stream.runForEachArray(r.stream, (chunk) => {
867
- const responses = chunk.flatMap(parser.decode) as Array<FromServerEncoded>
873
+ let hasResponse = false
874
+ yield* Stream.runForEachArray(response.stream, (chunk) =>
875
+ Effect.try({
876
+ try: () => chunk.flatMap(parser.decode) as Array<FromServerEncoded>,
877
+ catch: (cause) => protocolDefect("Error decoding HTTP response", cause)
878
+ }).pipe(
879
+ Effect.flatMap((responses) => {
868
880
  if (responses.length === 0) return Effect.void
881
+ hasResponse = true
869
882
  let i = 0
870
883
  return Effect.whileLoop({
871
884
  while: () => i < responses.length,
@@ -873,14 +886,13 @@ export const makeProtocolHttp = (client: HttpClient.HttpClient): Effect.Effect<
873
886
  step: constVoid
874
887
  })
875
888
  })
876
- ),
877
- Effect.mapError((cause) =>
878
- new RpcClientError({
879
- reason: HttpClientErrorSchema.fromHttpClientError(cause)
880
- })
889
+ )).pipe(
890
+ Effect.mapError((cause) => cause instanceof RpcClientError ? cause : httpClientError(cause))
881
891
  )
882
- )
883
- }
892
+ if (!hasResponse) {
893
+ return yield* Effect.fail(emptyResponseError(request))
894
+ }
895
+ })
884
896
 
885
897
  return {
886
898
  send,
@@ -392,7 +392,7 @@ export const makeChannel = <IE = never>(): Channel.Channel<
392
392
  /**
393
393
  * @since 4.0.0
394
394
  */
395
- export const defaultCloseCodeIsError = (code: number) => code !== 1000 && code !== 1006
395
+ export const defaultCloseCodeIsError = (_code: number) => true
396
396
 
397
397
  /**
398
398
  * @since 4.0.0
@@ -39,6 +39,14 @@ export interface Activity<
39
39
  readonly successSchema: Success
40
40
  readonly errorSchema: Error
41
41
  readonly exitSchema: Schema.Exit<Success, Error, Schema.Defect>
42
+ readonly annotations: ServiceMap.ServiceMap<never>
43
+ annotate<I, S>(
44
+ key: ServiceMap.Key<I, S>,
45
+ value: S
46
+ ): Activity<Success, Error, R>
47
+ annotateMerge<I>(
48
+ annotations: ServiceMap.ServiceMap<I>
49
+ ): Activity<Success, Error, R>
42
50
  readonly execute: Effect.Effect<
43
51
  Success["Type"],
44
52
  Error["Type"],
@@ -73,6 +81,7 @@ export interface Any {
73
81
  readonly [TypeId]: typeof TypeId
74
82
  readonly name: string
75
83
  readonly executeEncoded: Effect.Effect<any, any, any>
84
+ readonly annotations: ServiceMap.ServiceMap<never>
76
85
  }
77
86
 
78
87
  /**
@@ -101,6 +110,7 @@ export const make = <
101
110
  readonly error?: Error | undefined
102
111
  readonly execute: Effect.Effect<Success["Type"], Error["Type"], R>
103
112
  readonly interruptRetryPolicy?: Schedule.Schedule<any, Cause.Cause<unknown>> | undefined
113
+ readonly annotations?: ServiceMap.ServiceMap<never> | undefined
104
114
  }): Activity<Success, Error, Exclude<R, WorkflowInstance | WorkflowEngine | Scope>> => {
105
115
  const successSchema = options.success ?? (Schema.Void as any as Success)
106
116
  const errorSchema = options.error ?? (Schema.Never as any as Error)
@@ -120,6 +130,19 @@ export const make = <
120
130
  successSchema,
121
131
  errorSchema,
122
132
  exitSchema: Schema.Exit(successSchemaJson, errorSchemaJson, Schema.Defect),
133
+ annotations: options.annotations ?? ServiceMap.empty(),
134
+ annotate(tag: ServiceMap.Key<any, any>, value: any) {
135
+ return make({
136
+ ...options,
137
+ annotations: ServiceMap.add(self.annotations, tag, value)
138
+ })
139
+ },
140
+ annotateMerge(context: ServiceMap.ServiceMap<any>) {
141
+ return make({
142
+ ...options,
143
+ annotations: ServiceMap.merge(self.annotations, context)
144
+ })
145
+ },
123
146
  execute: executeWithoutInterrupt,
124
147
  executeEncoded: Effect.matchEffect(executeWithoutInterrupt, {
125
148
  onFailure: (error) => Effect.flatMap(Effect.orDie(Schema.encodeEffect(errorSchemaJson)(error)), Effect.fail),