effect 4.0.0-beta.10 → 4.0.0-beta.12

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 (189) hide show
  1. package/dist/Channel.d.ts +7 -7
  2. package/dist/Config.d.ts +157 -0
  3. package/dist/Config.d.ts.map +1 -1
  4. package/dist/Config.js +56 -1
  5. package/dist/Config.js.map +1 -1
  6. package/dist/Effect.d.ts +296 -8
  7. package/dist/Effect.d.ts.map +1 -1
  8. package/dist/Effect.js +72 -0
  9. package/dist/Effect.js.map +1 -1
  10. package/dist/ErrorReporter.d.ts +376 -0
  11. package/dist/ErrorReporter.d.ts.map +1 -0
  12. package/dist/ErrorReporter.js +246 -0
  13. package/dist/ErrorReporter.js.map +1 -0
  14. package/dist/Fiber.d.ts +2 -2
  15. package/dist/Fiber.d.ts.map +1 -1
  16. package/dist/Fiber.js.map +1 -1
  17. package/dist/Graph.d.ts.map +1 -1
  18. package/dist/Graph.js +3 -6
  19. package/dist/Graph.js.map +1 -1
  20. package/dist/LogLevel.d.ts +5 -0
  21. package/dist/LogLevel.d.ts.map +1 -1
  22. package/dist/LogLevel.js.map +1 -1
  23. package/dist/Logger.d.ts +25 -91
  24. package/dist/Logger.d.ts.map +1 -1
  25. package/dist/Logger.js +2 -3
  26. package/dist/Logger.js.map +1 -1
  27. package/dist/Queue.d.ts.map +1 -1
  28. package/dist/Queue.js +0 -1
  29. package/dist/Queue.js.map +1 -1
  30. package/dist/Random.d.ts +17 -0
  31. package/dist/Random.d.ts.map +1 -1
  32. package/dist/Random.js +17 -0
  33. package/dist/Random.js.map +1 -1
  34. package/dist/References.d.ts +3 -3
  35. package/dist/References.d.ts.map +1 -1
  36. package/dist/Schema.d.ts +3 -1
  37. package/dist/Schema.d.ts.map +1 -1
  38. package/dist/SchemaAST.d.ts.map +1 -1
  39. package/dist/SchemaAST.js +2 -1
  40. package/dist/SchemaAST.js.map +1 -1
  41. package/dist/Stream.d.ts +5 -5
  42. package/dist/index.d.ts +4 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +4 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/internal/effect.js +98 -33
  47. package/dist/internal/effect.js.map +1 -1
  48. package/dist/internal/hashMap.js +2 -2
  49. package/dist/internal/hashMap.js.map +1 -1
  50. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  51. package/dist/unstable/ai/LanguageModel.js +86 -14
  52. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  53. package/dist/unstable/ai/McpSchema.d.ts +112 -36
  54. package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
  55. package/dist/unstable/ai/McpSchema.js +47 -10
  56. package/dist/unstable/ai/McpSchema.js.map +1 -1
  57. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  58. package/dist/unstable/ai/McpServer.js +33 -6
  59. package/dist/unstable/ai/McpServer.js.map +1 -1
  60. package/dist/unstable/ai/Tool.d.ts +16 -0
  61. package/dist/unstable/ai/Tool.d.ts.map +1 -1
  62. package/dist/unstable/ai/Tool.js +14 -0
  63. package/dist/unstable/ai/Tool.js.map +1 -1
  64. package/dist/unstable/cli/CliOutput.js +37 -6
  65. package/dist/unstable/cli/CliOutput.js.map +1 -1
  66. package/dist/unstable/cli/Command.d.ts +199 -7
  67. package/dist/unstable/cli/Command.d.ts.map +1 -1
  68. package/dist/unstable/cli/Command.js +116 -6
  69. package/dist/unstable/cli/Command.js.map +1 -1
  70. package/dist/unstable/cli/HelpDoc.d.ts +60 -2
  71. package/dist/unstable/cli/HelpDoc.d.ts.map +1 -1
  72. package/dist/unstable/cli/internal/command.d.ts +11 -1
  73. package/dist/unstable/cli/internal/command.d.ts.map +1 -1
  74. package/dist/unstable/cli/internal/command.js +33 -8
  75. package/dist/unstable/cli/internal/command.js.map +1 -1
  76. package/dist/unstable/cli/internal/completions/CommandDescriptor.js +7 -2
  77. package/dist/unstable/cli/internal/completions/CommandDescriptor.js.map +1 -1
  78. package/dist/unstable/cli/internal/parser.js +10 -2
  79. package/dist/unstable/cli/internal/parser.js.map +1 -1
  80. package/dist/unstable/cluster/ClusterWorkflowEngine.d.ts.map +1 -1
  81. package/dist/unstable/cluster/ClusterWorkflowEngine.js +2 -2
  82. package/dist/unstable/cluster/ClusterWorkflowEngine.js.map +1 -1
  83. package/dist/unstable/http/Headers.d.ts.map +1 -1
  84. package/dist/unstable/http/Headers.js +27 -10
  85. package/dist/unstable/http/Headers.js.map +1 -1
  86. package/dist/unstable/http/HttpClient.d.ts +28 -4
  87. package/dist/unstable/http/HttpClient.d.ts.map +1 -1
  88. package/dist/unstable/http/HttpClient.js.map +1 -1
  89. package/dist/unstable/http/HttpEffect.d.ts +3 -8
  90. package/dist/unstable/http/HttpEffect.d.ts.map +1 -1
  91. package/dist/unstable/http/HttpEffect.js +25 -31
  92. package/dist/unstable/http/HttpEffect.js.map +1 -1
  93. package/dist/unstable/http/HttpMiddleware.d.ts.map +1 -1
  94. package/dist/unstable/http/HttpMiddleware.js +4 -8
  95. package/dist/unstable/http/HttpMiddleware.js.map +1 -1
  96. package/dist/unstable/http/HttpServerError.d.ts +14 -27
  97. package/dist/unstable/http/HttpServerError.d.ts.map +1 -1
  98. package/dist/unstable/http/HttpServerError.js +37 -44
  99. package/dist/unstable/http/HttpServerError.js.map +1 -1
  100. package/dist/unstable/http/HttpServerRespondable.d.ts +2 -2
  101. package/dist/unstable/http/HttpServerRespondable.d.ts.map +1 -1
  102. package/dist/unstable/http/HttpServerRespondable.js +5 -5
  103. package/dist/unstable/http/HttpServerRespondable.js.map +1 -1
  104. package/dist/unstable/http/HttpServerResponse.d.ts +2 -1
  105. package/dist/unstable/http/HttpServerResponse.d.ts.map +1 -1
  106. package/dist/unstable/http/HttpServerResponse.js +2 -0
  107. package/dist/unstable/http/HttpServerResponse.js.map +1 -1
  108. package/dist/unstable/http/internal/preResponseHandler.d.ts +2 -0
  109. package/dist/unstable/http/internal/preResponseHandler.d.ts.map +1 -0
  110. package/dist/unstable/http/internal/preResponseHandler.js +10 -0
  111. package/dist/unstable/http/internal/preResponseHandler.js.map +1 -0
  112. package/dist/unstable/httpapi/HttpApiBuilder.d.ts +1 -1
  113. package/dist/unstable/httpapi/HttpApiBuilder.d.ts.map +1 -1
  114. package/dist/unstable/httpapi/HttpApiBuilder.js +1 -1
  115. package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
  116. package/dist/unstable/httpapi/HttpApiError.d.ts +11 -0
  117. package/dist/unstable/httpapi/HttpApiError.d.ts.map +1 -1
  118. package/dist/unstable/httpapi/HttpApiError.js +29 -9
  119. package/dist/unstable/httpapi/HttpApiError.js.map +1 -1
  120. package/dist/unstable/observability/OtlpLogger.d.ts.map +1 -1
  121. package/dist/unstable/observability/OtlpLogger.js +7 -4
  122. package/dist/unstable/observability/OtlpLogger.js.map +1 -1
  123. package/dist/unstable/reactivity/Atom.js +1 -1
  124. package/dist/unstable/reactivity/Atom.js.map +1 -1
  125. package/dist/unstable/reactivity/AtomRegistry.d.ts +6 -0
  126. package/dist/unstable/reactivity/AtomRegistry.d.ts.map +1 -1
  127. package/dist/unstable/reactivity/AtomRegistry.js +22 -1
  128. package/dist/unstable/reactivity/AtomRegistry.js.map +1 -1
  129. package/dist/unstable/rpc/RpcSchema.d.ts +13 -0
  130. package/dist/unstable/rpc/RpcSchema.d.ts.map +1 -1
  131. package/dist/unstable/rpc/RpcSchema.js +14 -0
  132. package/dist/unstable/rpc/RpcSchema.js.map +1 -1
  133. package/dist/unstable/rpc/RpcSerialization.d.ts.map +1 -1
  134. package/dist/unstable/rpc/RpcSerialization.js +34 -9
  135. package/dist/unstable/rpc/RpcSerialization.js.map +1 -1
  136. package/dist/unstable/rpc/RpcServer.d.ts +0 -7
  137. package/dist/unstable/rpc/RpcServer.d.ts.map +1 -1
  138. package/dist/unstable/rpc/RpcServer.js +9 -10
  139. package/dist/unstable/rpc/RpcServer.js.map +1 -1
  140. package/dist/unstable/workflow/WorkflowEngine.d.ts +6 -0
  141. package/dist/unstable/workflow/WorkflowEngine.d.ts.map +1 -1
  142. package/dist/unstable/workflow/WorkflowEngine.js +131 -0
  143. package/dist/unstable/workflow/WorkflowEngine.js.map +1 -1
  144. package/package.json +1 -1
  145. package/src/Channel.ts +9 -9
  146. package/src/Config.ts +171 -9
  147. package/src/Effect.ts +315 -8
  148. package/src/ErrorReporter.ts +459 -0
  149. package/src/Fiber.ts +9 -2
  150. package/src/Graph.ts +16 -6
  151. package/src/LogLevel.ts +6 -0
  152. package/src/Logger.ts +28 -95
  153. package/src/Queue.ts +0 -1
  154. package/src/Random.ts +18 -0
  155. package/src/References.ts +4 -4
  156. package/src/Schema.ts +1 -1
  157. package/src/SchemaAST.ts +2 -1
  158. package/src/Stream.ts +7 -7
  159. package/src/index.ts +5 -0
  160. package/src/internal/effect.ts +205 -49
  161. package/src/internal/hashMap.ts +2 -2
  162. package/src/unstable/ai/LanguageModel.ts +117 -16
  163. package/src/unstable/ai/McpSchema.ts +57 -11
  164. package/src/unstable/ai/McpServer.ts +44 -6
  165. package/src/unstable/ai/Tool.ts +15 -0
  166. package/src/unstable/cli/CliOutput.ts +45 -6
  167. package/src/unstable/cli/Command.ts +298 -11
  168. package/src/unstable/cli/HelpDoc.ts +68 -2
  169. package/src/unstable/cli/internal/command.ts +47 -11
  170. package/src/unstable/cli/internal/completions/CommandDescriptor.ts +7 -2
  171. package/src/unstable/cli/internal/parser.ts +11 -3
  172. package/src/unstable/cluster/ClusterWorkflowEngine.ts +2 -2
  173. package/src/unstable/http/Headers.ts +28 -13
  174. package/src/unstable/http/HttpClient.ts +45 -10
  175. package/src/unstable/http/HttpEffect.ts +30 -44
  176. package/src/unstable/http/HttpMiddleware.ts +4 -14
  177. package/src/unstable/http/HttpServerError.ts +42 -45
  178. package/src/unstable/http/HttpServerRespondable.ts +6 -6
  179. package/src/unstable/http/HttpServerResponse.ts +3 -1
  180. package/src/unstable/http/internal/preResponseHandler.ts +15 -0
  181. package/src/unstable/httpapi/HttpApiBuilder.ts +2 -1
  182. package/src/unstable/httpapi/HttpApiError.ts +30 -9
  183. package/src/unstable/observability/OtlpLogger.ts +9 -5
  184. package/src/unstable/reactivity/Atom.ts +1 -1
  185. package/src/unstable/reactivity/AtomRegistry.ts +29 -1
  186. package/src/unstable/rpc/RpcSchema.ts +17 -0
  187. package/src/unstable/rpc/RpcSerialization.ts +44 -9
  188. package/src/unstable/rpc/RpcServer.ts +14 -19
  189. package/src/unstable/workflow/WorkflowEngine.ts +178 -0
@@ -2,6 +2,7 @@
2
2
  * @since 4.0.0
3
3
  */
4
4
  import * as Effect from "../../Effect.ts"
5
+ import * as ErrorReporter from "../../ErrorReporter.ts"
5
6
  import type * as FileSystem from "../../FileSystem.ts"
6
7
  import { dual } from "../../Function.ts"
7
8
  import type * as Inspectable from "../../Inspectable.ts"
@@ -28,7 +29,7 @@ const TypeId = "~effect/http/HttpServerResponse"
28
29
  * @since 4.0.0
29
30
  * @category models
30
31
  */
31
- export interface HttpServerResponse extends Inspectable.Inspectable, Pipeable {
32
+ export interface HttpServerResponse extends Inspectable.Inspectable, Pipeable, ErrorReporter.Reportable {
32
33
  readonly [TypeId]: typeof TypeId
33
34
  readonly status: number
34
35
  readonly statusText?: string | undefined
@@ -834,6 +835,7 @@ const Proto: Omit<
834
835
  > = {
835
836
  ...PipeInspectableProto,
836
837
  [TypeId]: TypeId,
838
+ [ErrorReporter.ignore]: true,
837
839
  toJSON(this: HttpServerResponse) {
838
840
  return {
839
841
  _id: "HttpServerResponse",
@@ -0,0 +1,15 @@
1
+ import * as Effect from "../../../Effect.ts"
2
+ import type { PreResponseHandler } from "../HttpEffect.ts"
3
+ import type { HttpServerRequest } from "../HttpServerRequest.ts"
4
+
5
+ /** @internal */
6
+ export const requestPreResponseHandlers = new WeakMap<HttpServerRequest, PreResponseHandler>()
7
+
8
+ /** @internal */
9
+ export const appendPreResponseHandlerUnsafe = (request: HttpServerRequest, handler: PreResponseHandler): void => {
10
+ const prev = requestPreResponseHandlers.get(request)
11
+ const next: PreResponseHandler = prev ?
12
+ (request, response) => Effect.flatMap(prev(request, response), (response) => handler(request, response))
13
+ : handler
14
+ requestPreResponseHandlers.set(request, next)
15
+ }
@@ -356,7 +356,7 @@ export const securitySetCookie = (
356
356
  self: HttpApiSecurity.ApiKey,
357
357
  value: string | Redacted.Redacted,
358
358
  options?: Cookie["options"]
359
- ): Effect.Effect<void> =>
359
+ ): Effect.Effect<void, never, HttpServerRequest> =>
360
360
  HttpEffect.appendPreResponseHandler((_req, response) =>
361
361
  Effect.orDie(
362
362
  Response.setCookie(response, self.key, stringOrRedacted(value), {
@@ -549,6 +549,7 @@ function handlerToRoute(
549
549
  return Response.isHttpServerResponse(response) ? response : yield* encodeSuccess(response)
550
550
  })
551
551
  ).pipe(
552
+ Effect.withErrorReporting,
552
553
  Effect.catch((error) => {
553
554
  if (Schema.isSchemaError(error)) {
554
555
  error = HttpApiSchemaError.fromSchemaError(error)
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @since 4.0.0
3
3
  */
4
+ import * as ErrorReporter from "../../ErrorReporter.ts"
4
5
  import * as Schema from "../../Schema.ts"
5
6
  import * as HttpApiSchema from "./HttpApiSchema.ts"
6
7
 
@@ -21,6 +22,8 @@ export class HttpApiSchemaError extends Schema.ErrorClass<HttpApiSchemaError>("e
21
22
  static fromSchemaError(error: Schema.SchemaError): HttpApiSchemaError {
22
23
  return new HttpApiSchemaError({ message: error.message })
23
24
  }
25
+
26
+ override readonly [ErrorReporter.ignore] = true
24
27
  }
25
28
 
26
29
  /**
@@ -32,7 +35,9 @@ export class BadRequest extends Schema.ErrorClass<BadRequest>("effect/HttpApiErr
32
35
  }, {
33
36
  description: "BadRequest",
34
37
  httpApiStatus: 400
35
- }) {}
38
+ }) {
39
+ override readonly [ErrorReporter.ignore] = true
40
+ }
36
41
 
37
42
  /**
38
43
  * @category NoContent errors
@@ -51,7 +56,9 @@ export class Unauthorized extends Schema.ErrorClass<Unauthorized>("effect/HttpAp
51
56
  }, {
52
57
  description: "Unauthorized",
53
58
  httpApiStatus: 401
54
- }) {}
59
+ }) {
60
+ override readonly [ErrorReporter.ignore] = true
61
+ }
55
62
 
56
63
  /**
57
64
  * @category NoContent errors
@@ -70,7 +77,9 @@ export class Forbidden extends Schema.ErrorClass<Forbidden>("effect/HttpApiError
70
77
  }, {
71
78
  description: "Forbidden",
72
79
  httpApiStatus: 403
73
- }) {}
80
+ }) {
81
+ override readonly [ErrorReporter.ignore] = true
82
+ }
74
83
 
75
84
  /**
76
85
  * @category NoContent errors
@@ -89,7 +98,9 @@ export class NotFound extends Schema.ErrorClass<NotFound>("effect/HttpApiError/N
89
98
  }, {
90
99
  description: "NotFound",
91
100
  httpApiStatus: 404
92
- }) {}
101
+ }) {
102
+ override readonly [ErrorReporter.ignore] = true
103
+ }
93
104
 
94
105
  /**
95
106
  * @category NoContent errors
@@ -108,7 +119,9 @@ export class MethodNotAllowed extends Schema.ErrorClass<MethodNotAllowed>("effec
108
119
  }, {
109
120
  description: "MethodNotAllowed",
110
121
  httpApiStatus: 405
111
- }) {}
122
+ }) {
123
+ override readonly [ErrorReporter.ignore] = true
124
+ }
112
125
 
113
126
  /**
114
127
  * @category NoContent errors
@@ -127,7 +140,9 @@ export class NotAcceptable extends Schema.ErrorClass<NotAcceptable>("effect/Http
127
140
  }, {
128
141
  description: "NotAcceptable",
129
142
  httpApiStatus: 406
130
- }) {}
143
+ }) {
144
+ override readonly [ErrorReporter.ignore] = true
145
+ }
131
146
 
132
147
  /**
133
148
  * @category NoContent errors
@@ -146,7 +161,9 @@ export class RequestTimeout extends Schema.ErrorClass<RequestTimeout>("effect/Ht
146
161
  }, {
147
162
  description: "RequestTimeout",
148
163
  httpApiStatus: 408
149
- }) {}
164
+ }) {
165
+ override readonly [ErrorReporter.ignore] = true
166
+ }
150
167
 
151
168
  /**
152
169
  * @category NoContent errors
@@ -165,7 +182,9 @@ export class Conflict extends Schema.ErrorClass<Conflict>("effect/HttpApiError/C
165
182
  }, {
166
183
  description: "Conflict",
167
184
  httpApiStatus: 409
168
- }) {}
185
+ }) {
186
+ override readonly [ErrorReporter.ignore] = true
187
+ }
169
188
 
170
189
  /**
171
190
  * @since 4.0.0
@@ -184,7 +203,9 @@ export class Gone extends Schema.ErrorClass<Gone>("effect/HttpApiError/Gone")({
184
203
  }, {
185
204
  description: "Gone",
186
205
  httpApiStatus: 410
187
- }) {}
206
+ }) {
207
+ override readonly [ErrorReporter.ignore] = true
208
+ }
188
209
 
189
210
  /**
190
211
  * @category NoContent errors
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import * as Arr from "../../Array.ts"
5
5
  import * as Cause from "../../Cause.ts"
6
+ import { Clock } from "../../Clock.ts"
6
7
  import * as Duration from "../../Duration.ts"
7
8
  import * as Effect from "../../Effect.ts"
8
9
  import type * as Layer from "../../Layer.ts"
@@ -66,7 +67,8 @@ export const make: (
66
67
  })
67
68
 
68
69
  const opts = {
69
- excludeLogSpans: options.excludeLogSpans ?? false
70
+ excludeLogSpans: options.excludeLogSpans ?? false,
71
+ clock: yield* Clock
70
72
  }
71
73
  return Logger.make((options) => {
72
74
  exporter.push(makeLogRecord(options, opts))
@@ -104,11 +106,13 @@ export interface LogsData {
104
106
 
105
107
  // internal
106
108
 
107
- const makeLogRecord = (options: Logger.Logger.Options<unknown>, opts: {
109
+ const makeLogRecord = (options: Logger.Options<unknown>, opts: {
108
110
  readonly excludeLogSpans: boolean
111
+ readonly clock: Clock
109
112
  }): ILogRecord => {
110
- const now = options.date.getTime()
111
- const nanosString = `${now}000000`
113
+ const now = opts.clock.currentTimeNanosUnsafe()
114
+ const nanosString = now.toString()
115
+ const nowMillis = options.date.getTime()
112
116
 
113
117
  const attributes = OtlpResource.entriesToAttributes(Object.entries(options.fiber.getRef(CurrentLogAnnotations)))
114
118
  attributes.push({
@@ -119,7 +123,7 @@ const makeLogRecord = (options: Logger.Logger.Options<unknown>, opts: {
119
123
  for (const [label, startTime] of options.fiber.getRef(CurrentLogSpans)) {
120
124
  attributes.push({
121
125
  key: `logSpan.${label}`,
122
- value: { stringValue: `${now - startTime}ms` }
126
+ value: { stringValue: `${nowMillis - startTime}ms` }
123
127
  })
124
128
  }
125
129
  }
@@ -2070,7 +2070,7 @@ export const searchParam = <S extends Schema.Codec<any, string> = never>(name: s
2070
2070
  window.removeEventListener("pushstate", handleUpdate)
2071
2071
  })
2072
2072
  const value = new URLSearchParams(window.location.search).get(name) || ""
2073
- return decode ? Exit.findErrorOption(decode(value)) : value as any
2073
+ return decode ? Exit.getSuccess(decode(value)) : value as any
2074
2074
  },
2075
2075
  (ctx, value: any) => {
2076
2076
  if (typeof window === "undefined") {
@@ -56,6 +56,8 @@ export interface AtomRegistry {
56
56
  }) => () => void
57
57
  readonly reset: () => void
58
58
  readonly dispose: () => void
59
+ onNodeAdded?: ((node: Node<any>) => void) | undefined
60
+ onNodeRemoved?: ((node: Node<any>) => void) | undefined
59
61
  }
60
62
 
61
63
  /**
@@ -65,6 +67,10 @@ export interface AtomRegistry {
65
67
  export interface Node<A> {
66
68
  readonly atom: Atom.Atom<A>
67
69
  readonly value: () => A
70
+ parents: Array<Node<any>>
71
+ children: Array<Node<any>>
72
+ listeners: Set<() => void>
73
+ currentState(): "uninitialized" | "stale" | "valid" | "removed"
68
74
  }
69
75
 
70
76
  /**
@@ -290,6 +296,8 @@ class RegistryImpl implements AtomRegistry {
290
296
  readonly defaultIdleTTL: number | undefined
291
297
  readonly scheduler: Scheduler
292
298
  readonly schedulerAsync: Scheduler
299
+ onNodeAdded?: ((node: Node<any>) => void) | undefined
300
+ onNodeRemoved?: ((node: Node<any>) => void) | undefined
293
301
 
294
302
  constructor(
295
303
  initialValues?: Iterable<readonly [Atom.Atom<any>, any]>,
@@ -386,6 +394,7 @@ class RegistryImpl implements AtomRegistry {
386
394
  if (node === undefined) {
387
395
  node = this.createNode(atom)
388
396
  this.nodes.set(key, node)
397
+ this.onNodeAdded?.(node)
389
398
  } else if (this.atomHasTtl(atom)) {
390
399
  this.removeNodeTimeout(node)
391
400
  }
@@ -436,6 +445,7 @@ class RegistryImpl implements AtomRegistry {
436
445
  } else {
437
446
  this.nodes.delete(atomKey(node.atom))
438
447
  node.remove()
448
+ this.onNodeRemoved?.(node)
439
449
  }
440
450
  }
441
451
 
@@ -450,6 +460,7 @@ class RegistryImpl implements AtomRegistry {
450
460
  if (idleTTL <= 0) {
451
461
  this.nodes.delete(atomKey(node.atom))
452
462
  node.remove()
463
+ this.onNodeRemoved?.(node)
453
464
  return
454
465
  }
455
466
  }
@@ -496,6 +507,7 @@ class RegistryImpl implements AtomRegistry {
496
507
  }
497
508
  this.nodeTimeoutBucket.delete(node)
498
509
  this.nodes.delete(atomKey(node.atom))
510
+ this.onNodeRemoved?.(node)
499
511
  this.#currentSweepTTL = node.atom.idleTTL ?? this.defaultIdleTTL!
500
512
  node.remove()
501
513
  this.#currentSweepTTL = null
@@ -507,7 +519,10 @@ class RegistryImpl implements AtomRegistry {
507
519
  this.timeoutBuckets.clear()
508
520
  this.nodeTimeoutBucket.clear()
509
521
 
510
- this.nodes.forEach((node) => node.remove())
522
+ this.nodes.forEach((node) => {
523
+ node.remove()
524
+ this.onNodeRemoved?.(node)
525
+ })
511
526
  this.nodes.clear()
512
527
  }
513
528
 
@@ -554,6 +569,19 @@ class NodeImpl<A> {
554
569
  listeners: Set<() => void> = new Set()
555
570
  skipInvalidation = false
556
571
 
572
+ currentState() {
573
+ switch (this.state) {
574
+ case NodeState.uninitialized:
575
+ return "uninitialized"
576
+ case NodeState.stale:
577
+ return "stale"
578
+ case NodeState.valid:
579
+ return "valid"
580
+ default:
581
+ return "removed"
582
+ }
583
+ }
584
+
557
585
  get canBeRemoved(): boolean {
558
586
  return !this.atom.keepAlive && this.listeners.size === 0 && this.children.length === 0 && this.state !== 0
559
587
  }
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * @since 4.0.0
3
3
  */
4
+ import * as Cause from "../../Cause.ts"
5
+ import { constUndefined } from "../../Function.ts"
4
6
  import * as Predicate from "../../Predicate.ts"
5
7
  import * as Schema from "../../Schema.ts"
6
8
  import type * as AST from "../../SchemaAST.ts"
9
+ import * as ServiceMap from "../../ServiceMap.ts"
7
10
  import * as Stream_ from "../../Stream.ts"
8
11
 
9
12
  const StreamSchemaTypeId = "~effect/rpc/RpcSchema/StreamSchema"
@@ -58,3 +61,17 @@ const schema = Schema.declare(Stream_.isStream)
58
61
  export function Stream<A extends Schema.Top, E extends Schema.Top>(success: A, error: E): Stream<A, E> {
59
62
  return Schema.make(schema.ast, { [StreamSchemaTypeId]: StreamSchemaTypeId, success, error })
60
63
  }
64
+
65
+ /**
66
+ * @since 4.0.0
67
+ * @category Cause annotations
68
+ */
69
+ export class ClientAbort extends ServiceMap.Service<ClientAbort, true>()("effect/rpc/RpcSchema/ClientAbort") {
70
+ static annotation = this.serviceMap(true).pipe(
71
+ ServiceMap.add(Cause.StackTrace, {
72
+ name: "ClientAbort",
73
+ stack: constUndefined,
74
+ parent: undefined
75
+ })
76
+ )
77
+ }
@@ -107,11 +107,7 @@ export const jsonRpc = (options?: {
107
107
  return decodeJsonRpcRaw(decoded, batches)
108
108
  },
109
109
  encode: (response) => {
110
- if (Array.isArray(response)) {
111
- if (response.length === 0) return undefined
112
- return JSON.stringify(response.map(encodeJsonRpcMessage))
113
- }
114
- const encoded = encodeJsonRpcRaw(response as any, batches)
110
+ const encoded = encodeJsonRpcResponse(response as any, batches)
115
111
  return encoded && JSON.stringify(encoded)
116
112
  }
117
113
  }
@@ -146,10 +142,7 @@ export const ndJsonRpc = (options?: {
146
142
  return messages
147
143
  },
148
144
  encode: (response) => {
149
- if (Array.isArray(response)) {
150
- return parser.encode(response.map(encodeJsonRpcMessage))
151
- }
152
- const encoded = encodeJsonRpcRaw(response as any, batches)
145
+ const encoded = encodeJsonRpcResponse(response as any, batches)
153
146
  return encoded && parser.encode(encoded)
154
147
  }
155
148
  })
@@ -171,6 +164,7 @@ function decodeJsonRpcRaw(
171
164
  const messages: Array<RpcMessage.FromClientEncoded | RpcMessage.FromServerEncoded> = []
172
165
  for (let i = 0; i < decoded.length; i++) {
173
166
  const message = decodeJsonRpcMessage(decoded[i])
167
+ messages.push(message)
174
168
  if (message._tag === "Request") {
175
169
  batch.size++
176
170
  batches.set(message.id, batch)
@@ -263,6 +257,47 @@ function encodeJsonRpcRaw(
263
257
  return encodeJsonRpcMessage(response)
264
258
  }
265
259
 
260
+ function encodeJsonRpcResponse(
261
+ response:
262
+ | RpcMessage.FromServerEncoded
263
+ | RpcMessage.FromClientEncoded
264
+ | Array<RpcMessage.FromServerEncoded | RpcMessage.FromClientEncoded>,
265
+ batches: Map<string, {
266
+ readonly size: number
267
+ readonly responses: Map<string, RpcMessage.FromServerEncoded>
268
+ }>
269
+ ) {
270
+ if (Array.isArray(response) === false) {
271
+ return encodeJsonRpcRaw(response, batches)
272
+ }
273
+ if (response.length === 0) {
274
+ return undefined
275
+ }
276
+ const encoded: Array<JsonRpcMessage | Array<JsonRpcMessage>> = []
277
+ for (let i = 0; i < response.length; i++) {
278
+ const current = encodeJsonRpcRaw(response[i], batches)
279
+ if (current !== undefined) {
280
+ encoded.push(current)
281
+ }
282
+ }
283
+ if (encoded.length === 0) {
284
+ return undefined
285
+ }
286
+ if (encoded.length === 1) {
287
+ return encoded[0]
288
+ }
289
+ const messages: Array<JsonRpcMessage> = []
290
+ for (let i = 0; i < encoded.length; i++) {
291
+ const current = encoded[i]
292
+ if (Array.isArray(current)) {
293
+ messages.push(...current)
294
+ } else {
295
+ messages.push(current)
296
+ }
297
+ }
298
+ return messages
299
+ }
300
+
266
301
  function encodeJsonRpcMessage(response: RpcMessage.FromServerEncoded | RpcMessage.FromClientEncoded): JsonRpcMessage {
267
302
  switch (response._tag) {
268
303
  case "Request":
@@ -8,6 +8,7 @@ import * as Effect from "../../Effect.ts"
8
8
  import * as Exit from "../../Exit.ts"
9
9
  import * as Fiber from "../../Fiber.ts"
10
10
  import { constant, constTrue, constVoid, identity } from "../../Function.ts"
11
+ import { reportCauseUnsafe } from "../../internal/effect.ts"
11
12
  import * as Latch from "../../Latch.ts"
12
13
  import * as Layer from "../../Layer.ts"
13
14
  import type * as Option from "../../Option.ts"
@@ -174,17 +175,16 @@ export const makeNoSerialization: <Rpcs extends Rpc.Any>(
174
175
  }
175
176
  case "Interrupt": {
176
177
  const fiber = client.fibers.get(message.requestId)
177
- return fiber
178
- ? Effect.forkDetach(
179
- Fiber.interruptAs(fiber, fiberIdClientInterrupt),
180
- { startImmediately: true }
181
- )
182
- : options.onFromServer({
183
- _tag: "Exit",
184
- clientId,
185
- requestId: message.requestId,
186
- exit: Exit.interrupt()
187
- })
178
+ if (fiber) {
179
+ fiber.interruptUnsafe(requestFiber.id, RpcSchema.ClientAbort.annotation)
180
+ return Effect.void
181
+ }
182
+ return options.onFromServer({
183
+ _tag: "Exit",
184
+ clientId,
185
+ requestId: message.requestId,
186
+ exit: Exit.interrupt()
187
+ })
188
188
  }
189
189
  case "Eof": {
190
190
  client.ended = true
@@ -273,6 +273,9 @@ export const makeNoSerialization: <Rpcs extends Rpc.Any>(
273
273
  requestId: request.id,
274
274
  exit
275
275
  })
276
+ if (exit._tag === "Failure") {
277
+ reportCauseUnsafe(Fiber.getCurrent()!, exit.cause)
278
+ }
276
279
  return close ? Effect.ensuring(write, close) : write
277
280
  })
278
281
  if (enableTracing) {
@@ -1278,14 +1281,6 @@ export const layerProtocolWorkerRunner: Layer.Layer<
1278
1281
  WorkerRunner.WorkerRunnerPlatform
1279
1282
  > = Layer.effect(Protocol)(makeProtocolWorkerRunner)
1280
1283
 
1281
- /**
1282
- * Fiber id used to indicate client induced interrupts
1283
- *
1284
- * @since 4.0.0
1285
- * @category Interruption
1286
- */
1287
- export const fiberIdClientInterrupt = -499
1288
-
1289
1284
  // internal
1290
1285
 
1291
1286
  const makeSocketProtocol: Effect.Effect<
@@ -4,7 +4,10 @@
4
4
  import type * as Cause from "../../Cause.ts"
5
5
  import * as Effect from "../../Effect.ts"
6
6
  import * as Exit from "../../Exit.ts"
7
+ import * as Fiber from "../../Fiber.ts"
8
+ import * as FiberMap from "../../FiberMap.ts"
7
9
  import * as Latch from "../../Latch.ts"
10
+ import * as Layer from "../../Layer.ts"
8
11
  import * as Option from "../../Option.ts"
9
12
  import * as Schedule from "../../Schedule.ts"
10
13
  import * as Schema from "../../Schema.ts"
@@ -498,4 +501,179 @@ const defaultRetrySchedule = Schedule.exponential(200, 1.5).pipe(
498
501
  Schedule.either(Schedule.spaced(30000))
499
502
  )
500
503
 
504
+ /**
505
+ * @since 4.0.0
506
+ * @category Layers
507
+ */
508
+ export const layer: Layer.Layer<WorkflowEngine> = Layer.effect(WorkflowEngine)(
509
+ Effect.gen(function*() {
510
+ const scope = yield* Effect.scope
511
+
512
+ const workflows = new Map<string, {
513
+ readonly workflow: Workflow.Any
514
+ readonly execute: (
515
+ payload: object,
516
+ executionId: string
517
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>
518
+ readonly scope: Scope.Scope
519
+ }>()
520
+
521
+ type ExecutionState = {
522
+ readonly payload: object
523
+ readonly execute: (
524
+ payload: object,
525
+ executionId: string
526
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>
527
+ readonly parent: string | undefined
528
+ instance: WorkflowInstance["Service"]
529
+ fiber: Fiber.Fiber<Workflow.Result<unknown, unknown>> | undefined
530
+ }
531
+ const executions = new Map<string, ExecutionState>()
532
+
533
+ type ActivityState = {
534
+ exit: Exit.Exit<Workflow.Result<unknown, unknown>> | undefined
535
+ }
536
+ const activities = new Map<string, ActivityState>()
537
+
538
+ const resume = Effect.fnUntraced(function*(executionId: string): Effect.fn.Return<void> {
539
+ const state = executions.get(executionId)
540
+ if (!state) return
541
+ const exit = state.fiber?.pollUnsafe()
542
+ if (exit && exit._tag === "Success" && exit.value._tag === "Complete") {
543
+ return
544
+ } else if (state.fiber && !exit) {
545
+ return
546
+ }
547
+
548
+ const entry = workflows.get(state.instance.workflow.name)!
549
+ const instance = WorkflowInstance.initial(state.instance.workflow, state.instance.executionId)
550
+ instance.interrupted = state.instance.interrupted
551
+ state.instance = instance
552
+ state.fiber = yield* state.execute(state.payload, state.instance.executionId).pipe(
553
+ Effect.onExit(() => {
554
+ if (!instance.interrupted) {
555
+ return Effect.void
556
+ }
557
+ instance.suspended = false
558
+ return Effect.withFiber((fiber) => Effect.interruptible(Fiber.interrupt(fiber)))
559
+ }),
560
+ Workflow.intoResult,
561
+ Effect.provideService(WorkflowInstance, instance),
562
+ Effect.provideService(WorkflowEngine, engine),
563
+ Effect.tap((result) => {
564
+ if (!state.parent || result._tag !== "Complete") {
565
+ return Effect.void
566
+ }
567
+ return Effect.forkIn(resume(state.parent), scope)
568
+ }),
569
+ Effect.forkIn(entry.scope)
570
+ )
571
+ })
572
+
573
+ const deferredResults = new Map<string, Exit.Exit<any, any>>()
574
+
575
+ const clocks = yield* FiberMap.make<string>()
576
+
577
+ const engine = makeUnsafe({
578
+ register: Effect.fnUntraced(function*(workflow, execute) {
579
+ workflows.set(workflow.name, {
580
+ workflow,
581
+ execute,
582
+ scope: yield* Effect.scope
583
+ })
584
+ }),
585
+ execute: Effect.fnUntraced(function*(workflow, options) {
586
+ const entry = workflows.get(workflow.name)
587
+ if (!entry) {
588
+ return yield* Effect.orDie(Effect.fail(`Workflow ${workflow.name} is not registered`))
589
+ }
590
+
591
+ let state = executions.get(options.executionId)
592
+ if (!state) {
593
+ state = {
594
+ payload: options.payload,
595
+ execute: entry.execute,
596
+ instance: WorkflowInstance.initial(workflow, options.executionId),
597
+ fiber: undefined,
598
+ parent: options.parent?.executionId
599
+ }
600
+ executions.set(options.executionId, state)
601
+ yield* resume(options.executionId)
602
+ }
603
+ if (options.discard) return
604
+ return (yield* Fiber.join(state.fiber!)) as any
605
+ }),
606
+ interrupt: Effect.fnUntraced(function*(_workflow, executionId) {
607
+ const state = executions.get(executionId)
608
+ if (!state) return
609
+ state.instance.interrupted = true
610
+ yield* resume(executionId)
611
+ }),
612
+ resume(_workflow, executionId) {
613
+ return resume(executionId)
614
+ },
615
+ activityExecute: Effect.fnUntraced(function*(activity, attempt) {
616
+ const instance = yield* WorkflowInstance
617
+ const activityId = `${instance.executionId}/${activity.name}/${attempt}`
618
+ let state = activities.get(activityId)
619
+ if (state) {
620
+ const exit = state.exit
621
+ if (exit && exit._tag === "Success" && exit.value._tag === "Suspended") {
622
+ state.exit = undefined
623
+ } else if (exit) {
624
+ return yield* exit
625
+ }
626
+ } else {
627
+ state = { exit: undefined }
628
+ activities.set(activityId, state)
629
+ }
630
+ const activityInstance = WorkflowInstance.initial(instance.workflow, instance.executionId)
631
+ activityInstance.interrupted = instance.interrupted
632
+ return yield* activity.executeEncoded.pipe(
633
+ Workflow.intoResult,
634
+ Effect.provideService(WorkflowInstance, activityInstance),
635
+ Effect.onExit((exit) => {
636
+ state.exit = exit
637
+ return Effect.void
638
+ })
639
+ )
640
+ }),
641
+ poll: (_workflow, executionId) =>
642
+ Effect.suspend(() => {
643
+ const state = executions.get(executionId)
644
+ if (!state) {
645
+ return Effect.succeed(undefined)
646
+ }
647
+ const exit = state.fiber?.pollUnsafe()
648
+ return exit ?? Effect.succeed(undefined)
649
+ }),
650
+ deferredResult: Effect.fnUntraced(function*(deferred) {
651
+ const instance = yield* WorkflowInstance
652
+ const id = `${instance.executionId}/${deferred.name}`
653
+ return deferredResults.get(id)
654
+ }),
655
+ deferredDone: (options) =>
656
+ Effect.suspend(() => {
657
+ const id = `${options.executionId}/${options.deferredName}`
658
+ if (deferredResults.has(id)) return Effect.void
659
+ deferredResults.set(id, options.exit)
660
+ return resume(options.executionId)
661
+ }),
662
+ scheduleClock: (workflow, options) =>
663
+ engine.deferredDone(options.clock.deferred, {
664
+ workflowName: workflow.name,
665
+ executionId: options.executionId,
666
+ deferredName: options.clock.deferred.name,
667
+ exit: Exit.void
668
+ }).pipe(
669
+ Effect.delay(options.clock.duration),
670
+ FiberMap.run(clocks, `${options.executionId}/${options.clock.name}`, { onlyIfMissing: true }),
671
+ Effect.asVoid
672
+ )
673
+ })
674
+
675
+ return engine
676
+ })
677
+ )
678
+
501
679
  const toJsonExit = Exit.map((value: any) => value ?? null)