@livestore/utils 0.4.0 → 0.5.0-dev.0

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 (247) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/NoopTracer.d.ts +7 -8
  3. package/dist/NoopTracer.d.ts.map +1 -1
  4. package/dist/NoopTracer.js +3 -18
  5. package/dist/NoopTracer.js.map +1 -1
  6. package/dist/NoopTracer.test.d.ts +2 -0
  7. package/dist/NoopTracer.test.d.ts.map +1 -0
  8. package/dist/NoopTracer.test.js +17 -0
  9. package/dist/NoopTracer.test.js.map +1 -0
  10. package/dist/browser/Opfs/Opfs.d.ts +179 -29
  11. package/dist/browser/Opfs/Opfs.d.ts.map +1 -1
  12. package/dist/browser/Opfs/Opfs.js +132 -307
  13. package/dist/browser/Opfs/Opfs.js.map +1 -1
  14. package/dist/browser/Opfs/debug-utils.d.ts.map +1 -1
  15. package/dist/browser/Opfs/debug-utils.js +5 -4
  16. package/dist/browser/Opfs/debug-utils.js.map +1 -1
  17. package/dist/browser/Opfs/utils.d.ts +8 -8
  18. package/dist/browser/Opfs/utils.d.ts.map +1 -1
  19. package/dist/browser/Opfs/utils.js +35 -26
  20. package/dist/browser/Opfs/utils.js.map +1 -1
  21. package/dist/browser/QuotaExceededError.d.ts +10 -9
  22. package/dist/browser/QuotaExceededError.d.ts.map +1 -1
  23. package/dist/browser/WebChannelBrowser.d.ts.map +1 -1
  24. package/dist/browser/WebChannelBrowser.js +21 -10
  25. package/dist/browser/WebChannelBrowser.js.map +1 -1
  26. package/dist/browser/WebError.d.ts +54 -86
  27. package/dist/browser/WebError.d.ts.map +1 -1
  28. package/dist/browser/WebError.js +91 -84
  29. package/dist/browser/WebError.js.map +1 -1
  30. package/dist/browser/WebError.test.js +6 -6
  31. package/dist/browser/WebError.test.js.map +1 -1
  32. package/dist/browser/WebLock.d.ts.map +1 -1
  33. package/dist/browser/WebLock.js +13 -13
  34. package/dist/browser/WebLock.js.map +1 -1
  35. package/dist/effect/Debug.d.ts +4 -7
  36. package/dist/effect/Debug.d.ts.map +1 -1
  37. package/dist/effect/Debug.js +32 -36
  38. package/dist/effect/Debug.js.map +1 -1
  39. package/dist/effect/Effect.d.ts +31 -22
  40. package/dist/effect/Effect.d.ts.map +1 -1
  41. package/dist/effect/Effect.js +51 -47
  42. package/dist/effect/Effect.js.map +1 -1
  43. package/dist/effect/Effect.test.d.ts +2 -0
  44. package/dist/effect/Effect.test.d.ts.map +1 -0
  45. package/dist/effect/Effect.test.js +72 -0
  46. package/dist/effect/Effect.test.js.map +1 -0
  47. package/dist/effect/Error.d.ts +4 -6
  48. package/dist/effect/Error.d.ts.map +1 -1
  49. package/dist/effect/Logger.d.ts +0 -6
  50. package/dist/effect/Logger.d.ts.map +1 -1
  51. package/dist/effect/Logger.js +0 -32
  52. package/dist/effect/Logger.js.map +1 -1
  53. package/dist/effect/OtelTracer.d.ts +1 -1
  54. package/dist/effect/OtelTracer.d.ts.map +1 -1
  55. package/dist/effect/OtelTracer.js +2 -3
  56. package/dist/effect/OtelTracer.js.map +1 -1
  57. package/dist/effect/RpcClient.d.ts +7 -7
  58. package/dist/effect/RpcClient.d.ts.map +1 -1
  59. package/dist/effect/RpcClient.js +72 -52
  60. package/dist/effect/RpcClient.js.map +1 -1
  61. package/dist/effect/Schedule.d.ts +1 -1
  62. package/dist/effect/Schedule.d.ts.map +1 -1
  63. package/dist/effect/Schedule.js +2 -2
  64. package/dist/effect/Schedule.js.map +1 -1
  65. package/dist/effect/Schema/debug-diff.d.ts +1 -1
  66. package/dist/effect/Schema/debug-diff.d.ts.map +1 -1
  67. package/dist/effect/Schema/debug-diff.js +5 -7
  68. package/dist/effect/Schema/debug-diff.js.map +1 -1
  69. package/dist/effect/Schema/debug-diff.test.js +7 -4
  70. package/dist/effect/Schema/debug-diff.test.js.map +1 -1
  71. package/dist/effect/Schema/index.d.ts +28 -20
  72. package/dist/effect/Schema/index.d.ts.map +1 -1
  73. package/dist/effect/Schema/index.js +55 -28
  74. package/dist/effect/Schema/index.js.map +1 -1
  75. package/dist/effect/Stream.d.ts +13 -5
  76. package/dist/effect/Stream.d.ts.map +1 -1
  77. package/dist/effect/Stream.js +19 -12
  78. package/dist/effect/Stream.js.map +1 -1
  79. package/dist/effect/Stream.test.js +9 -9
  80. package/dist/effect/Stream.test.js.map +1 -1
  81. package/dist/effect/Subscribable.d.ts +9 -3
  82. package/dist/effect/Subscribable.d.ts.map +1 -1
  83. package/dist/effect/Subscribable.js +20 -35
  84. package/dist/effect/Subscribable.js.map +1 -1
  85. package/dist/effect/SubscriptionRef.d.ts +5 -6
  86. package/dist/effect/SubscriptionRef.d.ts.map +1 -1
  87. package/dist/effect/SubscriptionRef.js +2 -3
  88. package/dist/effect/SubscriptionRef.js.map +1 -1
  89. package/dist/effect/TaskTracing.d.ts +1 -1
  90. package/dist/effect/TaskTracing.d.ts.map +1 -1
  91. package/dist/effect/TaskTracing.js +12 -16
  92. package/dist/effect/TaskTracing.js.map +1 -1
  93. package/dist/effect/WebChannel/WebChannel.d.ts +7 -7
  94. package/dist/effect/WebChannel/WebChannel.d.ts.map +1 -1
  95. package/dist/effect/WebChannel/WebChannel.js +64 -43
  96. package/dist/effect/WebChannel/WebChannel.js.map +1 -1
  97. package/dist/effect/WebChannel/WebChannel.test.js +15 -15
  98. package/dist/effect/WebChannel/WebChannel.test.js.map +1 -1
  99. package/dist/effect/WebChannel/broadcastChannelWithAck.d.ts.map +1 -1
  100. package/dist/effect/WebChannel/broadcastChannelWithAck.js +16 -9
  101. package/dist/effect/WebChannel/broadcastChannelWithAck.js.map +1 -1
  102. package/dist/effect/WebChannel/common.d.ts +16 -17
  103. package/dist/effect/WebChannel/common.d.ts.map +1 -1
  104. package/dist/effect/WebChannel/common.js +6 -6
  105. package/dist/effect/WebChannel/common.js.map +1 -1
  106. package/dist/effect/WebSocket.d.ts +4 -6
  107. package/dist/effect/WebSocket.d.ts.map +1 -1
  108. package/dist/effect/WebSocket.js +4 -4
  109. package/dist/effect/WebSocket.js.map +1 -1
  110. package/dist/effect/WebSocket.test.js +2 -2
  111. package/dist/effect/WebSocket.test.js.map +1 -1
  112. package/dist/effect/mod.browser-build.test.d.ts +2 -0
  113. package/dist/effect/mod.browser-build.test.d.ts.map +1 -0
  114. package/dist/effect/mod.browser-build.test.js +18 -0
  115. package/dist/effect/mod.browser-build.test.js.map +1 -0
  116. package/dist/effect/mod.d.ts +13 -15
  117. package/dist/effect/mod.d.ts.map +1 -1
  118. package/dist/effect/mod.js +13 -18
  119. package/dist/effect/mod.js.map +1 -1
  120. package/dist/effect/spanEvent.d.ts.map +1 -1
  121. package/dist/effect/spanEvent.js +2 -2
  122. package/dist/effect/spanEvent.js.map +1 -1
  123. package/dist/effect/spanEvent.test.js +10 -13
  124. package/dist/effect/spanEvent.test.js.map +1 -1
  125. package/dist/fast-deep-equal.d.ts.map +1 -1
  126. package/dist/fast-deep-equal.js +1 -2
  127. package/dist/fast-deep-equal.js.map +1 -1
  128. package/dist/misc.js +2 -2
  129. package/dist/misc.js.map +1 -1
  130. package/dist/mod.d.ts +0 -35
  131. package/dist/mod.d.ts.map +1 -1
  132. package/dist/mod.js +0 -8
  133. package/dist/mod.js.map +1 -1
  134. package/dist/node/mod.d.ts +17 -11
  135. package/dist/node/mod.d.ts.map +1 -1
  136. package/dist/node/mod.js +23 -16
  137. package/dist/node/mod.js.map +1 -1
  138. package/dist/object/index.d.ts +0 -9
  139. package/dist/object/index.d.ts.map +1 -1
  140. package/dist/object/index.js +0 -9
  141. package/dist/object/index.js.map +1 -1
  142. package/dist/string.d.ts +0 -2
  143. package/dist/string.d.ts.map +1 -1
  144. package/dist/string.js +0 -2
  145. package/dist/string.js.map +1 -1
  146. package/package.json +18 -36
  147. package/src/NoopTracer.test.ts +20 -0
  148. package/src/NoopTracer.ts +18 -41
  149. package/src/browser/Opfs/Opfs.ts +304 -277
  150. package/src/browser/Opfs/debug-utils.ts +7 -6
  151. package/src/browser/Opfs/utils.ts +36 -26
  152. package/src/browser/QuotaExceededError.ts +13 -10
  153. package/src/browser/WebChannelBrowser.ts +25 -11
  154. package/src/browser/WebError.test.ts +6 -6
  155. package/src/browser/WebError.ts +107 -133
  156. package/src/browser/WebLock.ts +13 -14
  157. package/src/effect/Debug.ts +66 -59
  158. package/src/effect/Effect.test.ts +117 -0
  159. package/src/effect/Effect.ts +84 -71
  160. package/src/effect/Logger.ts +0 -48
  161. package/src/effect/OtelTracer.ts +2 -3
  162. package/src/effect/RpcClient.ts +80 -61
  163. package/src/effect/Schedule.ts +4 -5
  164. package/src/effect/Schema/debug-diff.test.ts +6 -6
  165. package/src/effect/Schema/debug-diff.ts +6 -8
  166. package/src/effect/Schema/index.ts +122 -61
  167. package/src/effect/Stream.test.ts +9 -9
  168. package/src/effect/Stream.ts +23 -16
  169. package/src/effect/Subscribable.ts +31 -42
  170. package/src/effect/SubscriptionRef.ts +8 -12
  171. package/src/effect/TaskTracing.ts +13 -16
  172. package/src/effect/WebChannel/WebChannel.test.ts +23 -23
  173. package/src/effect/WebChannel/WebChannel.ts +87 -55
  174. package/src/effect/WebChannel/broadcastChannelWithAck.ts +21 -11
  175. package/src/effect/WebChannel/common.ts +14 -15
  176. package/src/effect/WebSocket.test.ts +2 -2
  177. package/src/effect/WebSocket.ts +9 -6
  178. package/src/effect/mod.browser-build.test.ts +19 -0
  179. package/src/effect/mod.ts +35 -69
  180. package/src/effect/spanEvent.test.ts +10 -13
  181. package/src/effect/spanEvent.ts +5 -3
  182. package/src/fast-deep-equal.ts +1 -2
  183. package/src/misc.ts +2 -2
  184. package/src/mod.ts +0 -36
  185. package/src/node/mod.ts +26 -20
  186. package/src/object/index.ts +0 -24
  187. package/src/string.ts +0 -3
  188. package/dist/effect/BucketQueue.d.ts +0 -20
  189. package/dist/effect/BucketQueue.d.ts.map +0 -1
  190. package/dist/effect/BucketQueue.js +0 -34
  191. package/dist/effect/BucketQueue.js.map +0 -1
  192. package/dist/effect/Scheduler.d.ts +0 -4
  193. package/dist/effect/Scheduler.d.ts.map +0 -1
  194. package/dist/effect/Scheduler.js +0 -9
  195. package/dist/effect/Scheduler.js.map +0 -1
  196. package/dist/effect/ServiceContext.d.ts +0 -37
  197. package/dist/effect/ServiceContext.d.ts.map +0 -1
  198. package/dist/effect/ServiceContext.js +0 -55
  199. package/dist/effect/ServiceContext.js.map +0 -1
  200. package/dist/guards.d.ts +0 -20
  201. package/dist/guards.d.ts.map +0 -1
  202. package/dist/guards.js +0 -20
  203. package/dist/guards.js.map +0 -1
  204. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts +0 -4
  205. package/dist/node/ChildProcessRunner/ChildProcessRunner.d.ts.map +0 -1
  206. package/dist/node/ChildProcessRunner/ChildProcessRunner.js +0 -128
  207. package/dist/node/ChildProcessRunner/ChildProcessRunner.js.map +0 -1
  208. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts +0 -10
  209. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.d.ts.map +0 -1
  210. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js +0 -217
  211. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.js.map +0 -1
  212. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts +0 -84
  213. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.d.ts.map +0 -1
  214. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js +0 -68
  215. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/schema.js.map +0 -1
  216. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts +0 -2
  217. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.d.ts.map +0 -1
  218. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js +0 -52
  219. package/dist/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.js.map +0 -1
  220. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts +0 -23
  221. package/dist/node/ChildProcessRunner/ChildProcessWorker.d.ts.map +0 -1
  222. package/dist/node/ChildProcessRunner/ChildProcessWorker.js +0 -138
  223. package/dist/node/ChildProcessRunner/ChildProcessWorker.js.map +0 -1
  224. package/dist/object/omit.d.ts +0 -3
  225. package/dist/object/omit.d.ts.map +0 -1
  226. package/dist/object/omit.js +0 -14
  227. package/dist/object/omit.js.map +0 -1
  228. package/dist/object/pick.d.ts +0 -14
  229. package/dist/object/pick.d.ts.map +0 -1
  230. package/dist/object/pick.js +0 -17
  231. package/dist/object/pick.js.map +0 -1
  232. package/dist/time.d.ts +0 -12
  233. package/dist/time.d.ts.map +0 -1
  234. package/dist/time.js +0 -22
  235. package/dist/time.js.map +0 -1
  236. package/src/effect/BucketQueue.ts +0 -49
  237. package/src/effect/Scheduler.ts +0 -13
  238. package/src/effect/ServiceContext.ts +0 -108
  239. package/src/guards.ts +0 -23
  240. package/src/node/ChildProcessRunner/ChildProcessRunner.ts +0 -148
  241. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/ChildProcessRunner.test.ts +0 -311
  242. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/schema.ts +0 -78
  243. package/src/node/ChildProcessRunner/ChildProcessRunnerTest/serializedWorker.ts +0 -65
  244. package/src/node/ChildProcessRunner/ChildProcessWorker.ts +0 -169
  245. package/src/object/omit.ts +0 -17
  246. package/src/object/pick.ts +0 -27
  247. package/src/time.ts +0 -25
@@ -1,22 +1,20 @@
1
- import * as OtelTracer from '@effect/opentelemetry/Tracer'
1
+ import * as OtelTracer from '@effect/opentelemetry/OtelTracer'
2
2
  import {
3
3
  Cause,
4
+ Console,
4
5
  type Context,
5
6
  Deferred,
6
7
  Duration,
7
8
  Effect,
9
+ Function,
8
10
  Fiber,
9
11
  pipe,
12
+ Predicate,
10
13
  Scope,
11
14
  type Stream,
12
15
  } from 'effect'
13
- import type { UnknownException } from 'effect/Cause'
14
- import { log } from 'effect/Console'
15
- import { dual, type LazyArg } from 'effect/Function'
16
- import type { Predicate, Refinement } from 'effect/Predicate'
17
16
 
18
- import { isDevEnv, isPromise, objectToString } from '../mod.ts'
19
- import { UnknownError } from './Error.ts'
17
+ import { isDevEnv, objectToString } from '../misc.ts'
20
18
 
21
19
  export * from 'effect/Effect'
22
20
  export { spanEvent } from './spanEvent.ts'
@@ -36,16 +34,14 @@ export { spanEvent } from './spanEvent.ts'
36
34
  // console.error(message, ...rest)
37
35
  // })
38
36
 
39
- /** Same as `Effect.scopeWith` but with a `CloseableScope` instead of a `Scope`. */
37
+ /** Same as `Effect.scopeWith` but with a closeable `Scope`. */
40
38
  export const scopeWithCloseable = <R, E, A>(
41
- fn: (scope: Scope.CloseableScope) => Effect.Effect<A, E, R | Scope.Scope>,
39
+ fn: (scope: Scope.Closeable) => Effect.Effect<A, E, R | Scope.Scope>,
42
40
  ): Effect.Effect<A, E, R | Scope.Scope> =>
43
41
  Effect.gen(function* () {
44
- // const parentScope = yield* Scope.Scope
45
- // const scope = yield* Scope.fork(parentScope, ExecutionStrategy.sequential)
46
42
  const scope = yield* Scope.make()
47
43
  yield* Effect.addFinalizer((exit) => Scope.close(scope, exit))
48
- return yield* fn(scope).pipe(Scope.extend(scope))
44
+ return yield* fn(scope).pipe(Scope.provide(scope))
49
45
  })
50
46
 
51
47
  export type SyncOrPromiseOrEffect<TResult, TError = never, TContext = never> =
@@ -53,22 +49,43 @@ export type SyncOrPromiseOrEffect<TResult, TError = never, TContext = never> =
53
49
  | Promise<TResult>
54
50
  | Effect.Effect<TResult, TError, TContext>
55
51
 
56
- export const tryAll = <Res>(
57
- fn: () => Res,
58
- ): Res extends Effect.Effect<infer A, infer E>
59
- ? Effect.Effect<A, E | UnknownException>
60
- : Res extends Promise<infer A>
61
- ? Effect.Effect<A, UnknownException>
62
- : Effect.Effect<Res, UnknownException> =>
63
- Effect.try(() => fn()).pipe(
64
- Effect.andThen((fnRes) =>
65
- Effect.isEffect(fnRes) === true
66
- ? (fnRes as any as Effect.Effect<any>)
67
- : isPromise(fnRes) === true
68
- ? Effect.promise(() => fnRes)
69
- : Effect.succeed(fnRes),
70
- ),
71
- ) as any
52
+ /**
53
+ * Creates an Effect from an operation that may return a value, a Promise-like value, or another Effect.
54
+ *
55
+ * @remarks
56
+ * Synchronous values succeed directly, and Promise-like values are awaited. Synchronous throws and Promise rejections
57
+ * are mapped to `Cause.UnknownError`. When the operation returns an Effect, that Effect is executed rather than
58
+ * produced as a successful value; its failures and service requirements are preserved.
59
+ *
60
+ * @param operation - A callback that may return a synchronous value, a Promise-like value, or an Effect
61
+ * @returns An Effect representing the normalized outcome of the operation
62
+ */
63
+ export function trySyncOrPromiseOrEffect<Return>(
64
+ operation: () => Return,
65
+ ): Effect.Effect<
66
+ Return extends Effect.Effect<infer A, infer _E, infer _R> ? A : Return extends PromiseLike<infer A> ? A : Return,
67
+ Cause.UnknownError | (Return extends Effect.Effect<infer _A, infer E, infer _R> ? E : never),
68
+ Return extends Effect.Effect<infer _A, infer _E, infer R> ? R : never
69
+ >
70
+ export function trySyncOrPromiseOrEffect(operation: () => unknown): Effect.Effect<unknown, unknown, unknown> {
71
+ return Effect.try({
72
+ try: () => {
73
+ const result = operation()
74
+
75
+ if (Effect.isEffect(result) === true) {
76
+ // @effect-diagnostics-next-line anyUnknownInErrorContext:off -- dynamic implementation boundary; the public overload preserves exact Effect error and requirement channels
77
+ return result
78
+ }
79
+
80
+ if (Predicate.isPromiseLike(result) === true) {
81
+ return Effect.tryPromise(() => result)
82
+ }
83
+
84
+ return Effect.succeed(result)
85
+ },
86
+ catch: (cause) => new Cause.UnknownError(cause),
87
+ }).pipe(Effect.flatten)
88
+ }
72
89
 
73
90
  export const acquireReleaseLog = (label: string) =>
74
91
  Effect.acquireRelease(Effect.log(`${label} acquire`), (_, ex) => Effect.log(`${label} release`, ex))
@@ -85,16 +102,11 @@ export const logBefore =
85
102
 
86
103
  /** Logs both on errors and defects */
87
104
  export const tapCauseLogPretty = <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
88
- Effect.tapErrorCause(eff, (cause) =>
105
+ Effect.tapCause(eff, (cause) =>
89
106
  Effect.gen(function* () {
90
- if (Cause.isInterruptedOnly(cause) === true) {
91
- // console.log('interrupted', Cause.pretty(err), err)
92
- return
93
- }
107
+ if (Cause.hasInterruptsOnly(cause) === true) return
94
108
 
95
- const span = yield* OtelTracer.currentOtelSpan.pipe(
96
- Effect.catchTag('NoSuchElementException', (_) => Effect.succeed(undefined)),
97
- )
109
+ const span = yield* OtelTracer.currentOtelSpan.pipe(Effect.catchTag('NoSuchElementError', (_) => Effect.void))
98
110
 
99
111
  const firstErrLine = cause.toString().split('\n')[0]
100
112
  yield* Effect.logError(firstErrLine, cause).pipe((_) =>
@@ -121,7 +133,7 @@ export const dieDebugger = (msg: string, ...args: ReadonlyArray<unknown>): Effec
121
133
  debugger
122
134
  void args // Keeps the variable in scope so it's inspectable when the debugger pauses
123
135
  }
124
- return Effect.dieMessage(msg)
136
+ return Effect.die(new Error(msg))
125
137
  })
126
138
 
127
139
  /**
@@ -136,27 +148,27 @@ export const orDieDebugger = <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Eff
136
148
  Effect.matchEffect(self, {
137
149
  onFailure: (error) =>
138
150
  // Keep the debugger hook so that `debugger` runs only when the wrapped effect actually fails, not while building the wrapper.
139
- Effect.dieSync(() => {
151
+ Effect.suspend(() => {
140
152
  if (isDevEnv() === true) {
141
153
  // oxlint-disable-next-line eslint(no-debugger) -- intentional breakpoint for impossible states during development
142
154
  debugger
143
155
  }
144
- return error
156
+ return Effect.die(error)
145
157
  }),
146
158
  onSuccess: Effect.succeed,
147
159
  })
148
160
 
149
161
  export const ignoreIf: {
150
162
  <E, EB extends E>(
151
- refinement: Refinement<NoInfer<E>, EB>,
163
+ refinement: Predicate.Refinement<NoInfer<E>, EB>,
152
164
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void, Exclude<E, EB>, R>
153
- <E>(predicate: Predicate<NoInfer<E>>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void, E, R>
165
+ <E>(predicate: Predicate.Predicate<NoInfer<E>>): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<void, E, R>
154
166
  <A, E, R, EB extends E>(
155
167
  self: Effect.Effect<A, E, R>,
156
- refinement: Refinement<E, EB>,
168
+ refinement: Predicate.Refinement<E, EB>,
157
169
  ): Effect.Effect<void, Exclude<E, EB>, R>
158
- <A, E, R>(self: Effect.Effect<A, E, R>, predicate: Predicate<E>): Effect.Effect<void, E, R>
159
- } = dual(2, <A, E, R>(self: Effect.Effect<A, E, R>, predicate: Predicate<E>) =>
170
+ <A, E, R>(self: Effect.Effect<A, E, R>, predicate: Predicate.Predicate<E>): Effect.Effect<void, E, R>
171
+ } = Function.dual(2, <A, E, R>(self: Effect.Effect<A, E, R>, predicate: Predicate.Predicate<E>) =>
160
172
  self.pipe(Effect.catchIf(predicate, () => Effect.void)),
161
173
  )
162
174
 
@@ -167,9 +179,9 @@ export const eventListener = <TEvent = unknown>(
167
179
  options?: { once?: boolean },
168
180
  ) =>
169
181
  Effect.gen(function* () {
170
- const runtime = yield* Effect.runtime()
182
+ const services = yield* Effect.context()
171
183
 
172
- const handlerFn = (event: TEvent) => handler(event).pipe(Effect.provide(runtime), Effect.runFork)
184
+ const handlerFn = (event: TEvent) => handler(event).pipe(Effect.runForkWith(services))
173
185
 
174
186
  target.addEventListener(type, handlerFn, { once: options?.once ?? false })
175
187
 
@@ -177,10 +189,10 @@ export const eventListener = <TEvent = unknown>(
177
189
  })
178
190
 
179
191
  export const logWarnIfTakesLongerThan =
180
- ({ label, duration }: { label: string; duration: Duration.DurationInput }) =>
192
+ ({ label, duration }: { label: string; duration: Duration.Input }) =>
181
193
  <R, E, A>(eff: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
182
194
  Effect.gen(function* () {
183
- const runtime = yield* Effect.runtime()
195
+ const services = yield* Effect.context()
184
196
 
185
197
  let tookLongerThanTimer = false
186
198
 
@@ -190,8 +202,7 @@ export const logWarnIfTakesLongerThan =
190
202
  // TODO include span info
191
203
  return Effect.logWarning(`${label}: Took longer than ${objectToString(duration)}ms`)
192
204
  }),
193
- Effect.provide(runtime),
194
- Effect.runFork,
205
+ Effect.runForkWith(services),
195
206
  )
196
207
 
197
208
  const start = Date.now()
@@ -228,7 +239,7 @@ export const logDuration =
228
239
  const start = Date.now()
229
240
  const res = yield* eff
230
241
  const end = Date.now()
231
- yield* Effect.log(`${label}: ${Duration.format(end - start)}`)
242
+ yield* Effect.log(`${label}: ${Duration.format(Duration.millis(end - start))}`)
232
243
  return res
233
244
  })
234
245
 
@@ -240,7 +251,7 @@ export const tapSync =
240
251
  export const debugLogEnv = (msg?: string): Effect.Effect<Context.Context<never>> =>
241
252
  pipe(
242
253
  Effect.context<never>(),
243
- Effect.tap((env) => log(msg ?? 'debugLogEnv', env)),
254
+ Effect.tap((env) => Console.log(msg ?? 'debugLogEnv', env)),
244
255
  )
245
256
 
246
257
  /**
@@ -250,25 +261,26 @@ export const debugLogEnv = (msg?: string): Effect.Effect<Context.Context<never>>
250
261
  *
251
262
  * This function allows you to enforce a time limit on the execution of an
252
263
  * effect. If the effect does not complete within the given duration, it dies
253
- * with a {@link Cause.TimeoutException} as an unchecked defect. Unlike
254
- * {@link Effect.timeout}, which adds `TimeoutException` to the error channel,
264
+ * with a {@link Cause.TimeoutError} as an unchecked defect. Unlike
265
+ * {@link Effect.timeout}, which adds `TimeoutError` to the error channel,
255
266
  * this function keeps the error channel unchanged by treating the timeout as
256
267
  * a defect.
257
268
  *
258
269
  * The returned effect will either:
259
270
  * - Succeed with the original effect's result if it completes within the
260
271
  * specified duration.
261
- * - Die with a {@link Cause.TimeoutException} defect if the time limit is exceeded.
272
+ * - Die with a {@link Cause.TimeoutError} defect if the time limit is exceeded.
262
273
  *
263
274
  * @see {@link timeoutOrDieMessage} for a version with a custom message.
264
- * @see {@link Effect.timeout} for a version that raises a `TimeoutException` as a typed error.
265
- * @see {@link Effect.timeoutFailCause} for a version that raises a custom defect.
275
+ * @see {@link Effect.timeout} for a version that raises a `TimeoutError` as a typed error.
276
+ * @see {@link Effect.timeoutOrElse} for a version with a custom timeout branch.
266
277
  */
267
- export const timeoutOrDie = (duration: Duration.DurationInput) =>
278
+ export const timeoutOrDie =
279
+ (duration: Duration.Input) =>
268
280
  <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
269
- Effect.timeoutFailCause(self, {
281
+ Effect.timeoutOrElse(self, {
270
282
  duration,
271
- onTimeout: () => Cause.die(new Cause.TimeoutException())
283
+ orElse: () => Effect.die(new Cause.TimeoutError()),
272
284
  })
273
285
 
274
286
  /**
@@ -278,25 +290,26 @@ export const timeoutOrDie = (duration: Duration.DurationInput) =>
278
290
  * @remarks
279
291
  *
280
292
  * This function behaves like {@link timeoutOrDie}, but allows you to provide
281
- * a custom message for the {@link Cause.TimeoutException} defect. This is useful
293
+ * a custom message for the {@link Cause.TimeoutError} defect. This is useful
282
294
  * for adding context about which operation timed out, making it easier to
283
295
  * diagnose issues in logs or error reports.
284
296
  *
285
297
  * The returned effect will either:
286
298
  * - Succeed with the original effect's result if it completes within the
287
299
  * specified duration.
288
- * - Die with a {@link Cause.TimeoutException} defect containing the provided
300
+ * - Die with a {@link Cause.TimeoutError} defect containing the provided
289
301
  * message if the time limit is exceeded.
290
302
  *
291
303
  * @see {@link timeoutOrDie} for a version without a custom message.
292
- * @see {@link Effect.timeout} for a version that raises a `TimeoutException` as a typed error.
293
- * @see {@link Effect.timeoutFailCause} for a version that raises a custom defect.
304
+ * @see {@link Effect.timeout} for a version that raises a `TimeoutError` as a typed error.
305
+ * @see {@link Effect.timeoutOrElse} for a version with a custom timeout branch.
294
306
  */
295
- export const timeoutOrDieMessage = (duration: Duration.DurationInput, message: string) =>
307
+ export const timeoutOrDieMessage =
308
+ (duration: Duration.Input, message: string) =>
296
309
  <A, E, R>(self: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
297
- Effect.timeoutFailCause(self, {
310
+ Effect.timeoutOrElse(self, {
298
311
  duration,
299
- onTimeout: () => Cause.die(new Cause.TimeoutException(message))
312
+ orElse: () => Effect.die(new Cause.TimeoutError(message)),
300
313
  })
301
314
 
302
315
  export const toForkedDeferred = <R, E, A>(
@@ -328,16 +341,16 @@ export const withPerformanceMeasure =
328
341
  )
329
342
 
330
343
  const getSpanTrace = () => {
331
- const fiberOption = Fiber.getCurrentFiber()
332
- if (fiberOption._tag === 'None' || fiberOption.value.currentSpan === undefined) {
344
+ const fiber = Fiber.getCurrent()
345
+ if (fiber === undefined || fiber.currentSpan === undefined) {
333
346
  return 'No current fiber'
334
347
  }
335
348
 
336
349
  return ''
337
350
  // const msg = Effect.runSync(
338
351
  // Effect.fail({ message: '' }).pipe(
339
- // Effect.withParentSpan(fiberOption.value.currentSpan),
340
- // Effect.catchAllCause((cause) => Effect.succeed(cause.toString())),
352
+ // Effect.withParentSpan(fiber.currentSpan),
353
+ // Effect.catchCause((cause) => Effect.succeed(cause.toString())),
341
354
  // ),
342
355
  // )
343
356
 
@@ -1,49 +1 @@
1
- import { Cause, HashMap, Logger, LogLevel } from 'effect'
2
-
3
1
  export * from 'effect/Logger'
4
-
5
- const defaultDateFormat = (date: Date): string =>
6
- `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date
7
- .getSeconds()
8
- .toString()
9
- .padStart(2, '0')}.${date.getMilliseconds().toString().padStart(3, '0')}`
10
-
11
- export const prettyWithThread = (threadName: string, options: { mode?: 'tty' | 'browser' } = {}) =>
12
- Logger.replace(
13
- Logger.defaultLogger,
14
- Logger.prettyLogger({
15
- formatDate: (date) => `${defaultDateFormat(date)} ${threadName}`,
16
- mode: options.mode,
17
- }),
18
- )
19
-
20
- export const consoleLogger = (threadName: string) =>
21
- Logger.make(({ message, annotations, date, logLevel, cause }) => {
22
- const isCloudflareWorker = typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers'
23
- const consoleFn =
24
- logLevel === LogLevel.Debug
25
- ? // Cloudflare Workers doesn't support console.debug 🤷
26
- isCloudflareWorker === true
27
- ? console.log
28
- : console.debug
29
- : logLevel === LogLevel.Info
30
- ? console.info
31
- : logLevel === LogLevel.Warning
32
- ? console.warn
33
- : console.error
34
-
35
- const annotationsObj = Object.fromEntries(HashMap.entries(annotations))
36
-
37
- const messages = Array.isArray(message) === true ? message : [message]
38
- if (Cause.isEmpty(cause) === false) {
39
- messages.push(Cause.pretty(cause, { renderErrorCause: true }))
40
- }
41
-
42
- if (Object.keys(annotationsObj).length > 0) {
43
- messages.push(annotationsObj)
44
- }
45
-
46
- consoleFn(`[${defaultDateFormat(date)} ${threadName}]`, ...messages)
47
- })
48
-
49
- export const consoleWithThread = (threadName: string) => Logger.replace(Logger.defaultLogger, consoleLogger(threadName))
@@ -1,11 +1,10 @@
1
- import { makeExternalSpan } from '@effect/opentelemetry/Tracer'
1
+ import { makeExternalSpan } from '@effect/opentelemetry/OtelTracer'
2
2
  import type { Link as OtelSpanLink } from '@opentelemetry/api'
3
3
  import type { SpanLink as EffectSpanLink } from 'effect/Tracer'
4
4
 
5
- export * from '@effect/opentelemetry/Tracer'
5
+ export * from '@effect/opentelemetry/OtelTracer'
6
6
 
7
7
  export const makeSpanLink = (otelSpanLink: OtelSpanLink): EffectSpanLink => ({
8
- _tag: 'SpanLink',
9
8
  span: makeExternalSpan(otelSpanLink.context),
10
9
  attributes: otelSpanLink.attributes ?? {},
11
10
  })
@@ -1,11 +1,10 @@
1
- export * from '@effect/rpc/RpcClient'
1
+ export * from 'effect/unstable/rpc/RpcClient'
2
2
 
3
- import { Socket } from '@effect/platform'
4
- import { RpcClient, RpcClientError, RpcSerialization } from '@effect/rpc'
5
- import { Protocol } from '@effect/rpc/RpcClient'
6
- import { constPing, type FromServerEncoded } from '@effect/rpc/RpcMessage'
7
- import { Cause, Deferred, Effect, Layer, Option, Schedule, type Scope } from 'effect'
8
- import { constVoid, identity } from 'effect/Function'
3
+ import { Cause, Deferred, Effect, Function, Latch, Layer, References, Result, Schedule, type Scope } from 'effect'
4
+ import { RpcClient, RpcClientError, RpcSerialization } from 'effect/unstable/rpc'
5
+ import { Protocol } from 'effect/unstable/rpc/RpcClient'
6
+ import { constPing, type FromServerEncoded } from 'effect/unstable/rpc/RpcMessage'
7
+ import { Socket } from 'effect/unstable/socket'
9
8
 
10
9
  import * as SubscriptionRef from './SubscriptionRef.ts'
11
10
 
@@ -20,7 +19,7 @@ export const layerProtocolSocketWithIsConnected = (options: {
20
19
  readonly isConnected: SubscriptionRef.SubscriptionRef<boolean>
21
20
  readonly pingSchedule?: Schedule.Schedule<unknown> | undefined
22
21
  }): Layer.Layer<Protocol, never, RpcSerialization.RpcSerialization | Socket.Socket> =>
23
- Layer.scoped(Protocol, makeProtocolSocketWithIsConnected(options))
22
+ Layer.effect(Protocol, makeProtocolSocketWithIsConnected(options))
24
23
 
25
24
  export const makeProtocolSocketWithIsConnected = (options: {
26
25
  readonly url: string
@@ -29,16 +28,22 @@ export const makeProtocolSocketWithIsConnected = (options: {
29
28
  readonly isConnected: SubscriptionRef.SubscriptionRef<boolean>
30
29
  // CHANGED: add ping schedule
31
30
  readonly pingSchedule?: Schedule.Schedule<unknown> | undefined
32
- }): Effect.Effect<Protocol['Type'], never, Scope.Scope | RpcSerialization.RpcSerialization | Socket.Socket> =>
31
+ }): Effect.Effect<Protocol['Service'], never, Scope.Scope | RpcSerialization.RpcSerialization | Socket.Socket> =>
33
32
  Protocol.make(
34
- Effect.fnUntraced(function* (writeResponse) {
33
+ Effect.fnUntraced(function* (writeResponse, clientIds) {
35
34
  const socket = yield* Socket.Socket
36
35
  const serialization = yield* RpcSerialization.RpcSerialization
36
+ const requestClientMap = new Map<string | number, number>()
37
37
 
38
38
  const write = yield* socket.writer
39
- const parser = serialization.unsafeMake()
39
+ let parser = serialization.makeUnsafe()
40
40
 
41
41
  const pinger = yield* makePinger(write(parser.encode(constPing)!), options?.pingSchedule)
42
+ let currentError: RpcClientError.RpcClientError | undefined
43
+
44
+ const markConnected = SubscriptionRef.set(options.isConnected, true)
45
+ const broadcast = (response: FromServerEncoded) =>
46
+ Effect.forEach(clientIds, (clientId) => writeResponse(clientId, response))
42
47
 
43
48
  yield* Effect.suspend(() => {
44
49
  // We rely on the heartbeat watchdog while streaming arbitrarily long payloads.
@@ -46,7 +51,8 @@ export const makeProtocolSocketWithIsConnected = (options: {
46
51
  // don't contain explicit `Pong` messages don't trigger the open-timeout defect.
47
52
  // (The actual pong handler still calls `onPong()` to resolve manual pings.)
48
53
  // CHANGED: don't reset parser on every message
49
- // parser = serialization.unsafeMake()
54
+ // parser = serialization.makeUnsafe()
55
+ currentError = undefined
50
56
  pinger.reset()
51
57
  return socket
52
58
  .runRaw((message) => {
@@ -63,94 +69,107 @@ export const makeProtocolSocketWithIsConnected = (options: {
63
69
  pinger.reset()
64
70
  if (response._tag === 'Pong') {
65
71
  pinger.onPong()
72
+ return markConnected
73
+ }
74
+ if ('requestId' in response) {
75
+ const clientId = requestClientMap.get(response.requestId)
76
+ if (clientId !== undefined) {
77
+ if (response._tag === 'Exit') {
78
+ requestClientMap.delete(response.requestId)
79
+ }
80
+ return markConnected.pipe(Effect.andThen(writeResponse(clientId, response)))
81
+ }
66
82
  }
67
- return writeResponse(response).pipe(
68
- // CHANGED: set isConnected to true on pong
69
- Effect.tap(
70
- Effect.fn(function* () {
71
- if (options?.isConnected !== undefined) {
72
- yield* SubscriptionRef.set(options.isConnected, true)
73
- }
74
- }),
75
- ),
76
- )
83
+ return markConnected.pipe(Effect.andThen(broadcast(response)))
77
84
  },
78
- step: constVoid,
85
+ step: Function.constVoid,
79
86
  })
80
87
  } catch (defect) {
81
- return writeResponse({
88
+ return broadcast({
82
89
  _tag: 'ClientProtocolError',
83
90
  error: new RpcClientError.RpcClientError({
84
- reason: 'Protocol',
85
- message: 'Error decoding message',
86
- cause: Cause.fail(defect),
91
+ reason: new RpcClientError.RpcClientDefect({
92
+ message: 'Error decoding message',
93
+ cause: defect,
94
+ }),
87
95
  }),
88
96
  })
89
97
  }
90
98
  })
91
99
  .pipe(
92
100
  Effect.raceFirst(
93
- Effect.zipRight(
94
- pinger.timeout,
101
+ Effect.flatMap(pinger.timeout, () =>
95
102
  Effect.fail(
96
- new Socket.SocketGenericError({
97
- reason: 'OpenTimeout',
98
- cause: new Error('ping timeout'),
103
+ new Socket.SocketError({
104
+ reason: new Socket.SocketOpenError({
105
+ kind: 'Timeout',
106
+ cause: new Error('ping timeout'),
107
+ }),
99
108
  }),
100
109
  ),
101
110
  ),
102
111
  ),
103
112
  )
104
113
  }).pipe(
105
- Effect.zipRight(
114
+ Effect.flatMap(() =>
106
115
  Effect.fail(
107
- new Socket.SocketCloseError({
108
- reason: 'Close',
109
- code: 1000,
110
- closeReason: 'Closing connection',
116
+ new Socket.SocketError({
117
+ reason: new Socket.SocketCloseError({
118
+ code: 1000,
119
+ closeReason: 'Closing connection',
120
+ }),
111
121
  }),
112
122
  ),
113
123
  ),
114
- Effect.tapErrorCause(
124
+ Effect.tapCause(
115
125
  Effect.fn(function* (cause) {
116
126
  // CHANGED: set isConnected to false on error
117
- if (options?.isConnected !== undefined) {
118
- yield* SubscriptionRef.set(options.isConnected, false)
119
- }
127
+ yield* SubscriptionRef.set(options.isConnected, false)
120
128
 
121
- const error = Cause.failureOption(cause)
129
+ const error = Cause.findError(cause)
122
130
  if (
123
131
  options?.retryTransientErrors !== undefined &&
124
- Option.isSome(error) === true &&
125
- (error.value.reason === 'Open' || error.value.reason === 'OpenTimeout')
132
+ Result.isSuccess(error) === true &&
133
+ error.success.reason._tag === 'SocketOpenError'
126
134
  ) {
127
135
  return
128
136
  }
129
137
  // yield* Effect.logError('Error in socket', cause)
130
- return yield* writeResponse({
138
+ currentError = new RpcClientError.RpcClientError({
139
+ reason:
140
+ Result.isSuccess(error) === true
141
+ ? error.success.reason
142
+ : new RpcClientError.RpcClientDefect({
143
+ message: 'Unknown socket error',
144
+ cause: Cause.squash(cause),
145
+ }),
146
+ })
147
+ return yield* broadcast({
131
148
  _tag: 'ClientProtocolError',
132
- error: new RpcClientError.RpcClientError({
133
- reason: 'Protocol',
134
- message: 'Error in socket',
135
- cause: Cause.squash(cause),
136
- }),
149
+ error: currentError,
137
150
  })
138
151
  }),
139
152
  ),
140
153
  // CHANGED: make configurable via schedule
141
- options?.retryTransientErrors !== undefined ? Effect.retry(options.retryTransientErrors) : identity,
154
+ options?.retryTransientErrors !== undefined ? Effect.retry(options.retryTransientErrors) : Function.identity,
142
155
  Effect.annotateLogs({
143
156
  module: 'RpcClient',
144
157
  method: 'makeProtocolSocket',
145
158
  }),
146
159
  Effect.interruptible,
147
160
  Effect.ignore, // Errors are already handled
148
- Effect.provide(Layer.setUnhandledErrorLogLevel(Option.none())),
161
+ Effect.provideService(References.UnhandledLogLevel, undefined),
149
162
  Effect.forkScoped,
150
163
  )
151
164
 
152
165
  return {
153
- send: (request) => {
166
+ send: (clientId, request) => {
167
+ if (currentError !== undefined) {
168
+ return Effect.fail(currentError)
169
+ }
170
+ if (request._tag === 'Request') {
171
+ requestClientMap.set(request.id, clientId)
172
+ }
154
173
  const encoded = parser.encode(request)
155
174
  if (encoded === undefined) return Effect.void
156
175
 
@@ -165,33 +184,33 @@ export const makeProtocolSocketWithIsConnected = (options: {
165
184
 
166
185
  export const SocketPinger = Effect.map(RpcClient.Protocol, (protocol) => (protocol as any).pinger as SocketPinger)
167
186
 
168
- export type SocketPinger = Effect.Effect.Success<ReturnType<typeof makePinger>>
187
+ export type SocketPinger = Effect.Success<ReturnType<typeof makePinger>>
169
188
 
170
189
  const makePinger = Effect.fnUntraced(function* <A, E, R>(
171
190
  writePing: Effect.Effect<A, E, R>,
172
- pingSchedule: Schedule.Schedule<unknown> = Schedule.spaced(10000).pipe(Schedule.addDelay(() => 5000)),
191
+ pingSchedule: Schedule.Schedule<unknown> = Schedule.spaced(10000).pipe(Schedule.addDelay(() => Effect.succeed(5000))),
173
192
  ) {
174
193
  // CHANGED: add manual ping deferreds
175
194
  const manualPingDeferreds = new Set<Deferred.Deferred<void>>()
176
195
 
177
196
  let recievedPong = true
178
- const latch = Effect.unsafeMakeLatch()
197
+ const latch = Latch.makeUnsafe()
179
198
  const reset = () => {
180
199
  recievedPong = true
181
- latch.unsafeClose()
200
+ latch.closeUnsafe()
182
201
  }
183
202
  const onPong = () => {
184
203
  recievedPong = true
185
204
  // CHANGED: mark all manual ping deferreds as done
186
205
  for (const deferred of manualPingDeferreds) {
187
- Deferred.unsafeDone(deferred, Effect.void)
206
+ Deferred.doneUnsafe(deferred, Effect.void)
188
207
  }
189
208
  }
190
209
  yield* Effect.suspend(() => {
191
210
  // Starting new ping
192
- if (recievedPong === false) return latch.open
211
+ if (recievedPong === false) return Effect.asVoid(latch.open)
193
212
  recievedPong = false
194
- return writePing
213
+ return Effect.asVoid(writePing)
195
214
  }).pipe(
196
215
  // CHANGED: make configurable via schedule
197
216
  Effect.schedule(pingSchedule),
@@ -205,7 +224,7 @@ const makePinger = Effect.fnUntraced(function* <A, E, R>(
205
224
  const ping = Effect.gen(function* () {
206
225
  const deferred = yield* Deferred.make<void>()
207
226
  manualPingDeferreds.add(deferred)
208
- yield* deferred
227
+ yield* Deferred.await(deferred)
209
228
  manualPingDeferreds.delete(deferred)
210
229
  })
211
230
 
@@ -1,10 +1,9 @@
1
- import { Duration, pipe, Schedule } from 'effect'
1
+ import { Duration, Effect, pipe, Schedule } from 'effect'
2
2
 
3
3
  export * from 'effect/Schedule'
4
4
 
5
- export const exponentialBackoff10Sec: Schedule.Schedule<Duration.DurationInput> = pipe(
5
+ export const exponentialBackoff10Sec = pipe(
6
6
  Schedule.exponential(Duration.millis(10), 4), // 10ms, 40ms, 160ms, 640ms, 2560ms, ...
7
- Schedule.andThenEither(Schedule.spaced(Duration.seconds(1))),
8
- Schedule.compose(Schedule.elapsed),
9
- Schedule.whileOutput(Duration.lessThanOrEqualTo(Duration.seconds(10))), // max 10 seconds
7
+ Schedule.modifyDelay(({ duration }) => Effect.succeed(Duration.min(duration, Duration.seconds(1)))),
8
+ Schedule.upTo({ duration: Duration.seconds(10) }),
10
9
  )