@effect/workflow 0.1.3 → 0.1.5

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.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
+ import type { NonEmptyReadonlyArray } from "effect/Array"
4
5
  import type * as Brand from "effect/Brand"
5
6
  import type * as Cause from "effect/Cause"
6
7
  import * as Context from "effect/Context"
@@ -10,7 +11,7 @@ import * as Exit from "effect/Exit"
10
11
  import { dual } from "effect/Function"
11
12
  import * as Option from "effect/Option"
12
13
  import * as Schema from "effect/Schema"
13
- import type * as Workflow from "./Workflow.js"
14
+ import * as Workflow from "./Workflow.js"
14
15
  import type { WorkflowEngine, WorkflowInstance } from "./WorkflowEngine.js"
15
16
 
16
17
  /**
@@ -94,7 +95,7 @@ const await_: <Success extends Schema.Schema.Any, Error extends Schema.Schema.Al
94
95
  >(self: DurableDeferred<Success, Error>) {
95
96
  const engine = yield* EngineTag
96
97
  const instance = yield* InstanceTag
97
- const oexit = yield* engine.deferredResult(self)
98
+ const oexit = yield* Workflow.wrapActivityResult(engine.deferredResult(self), Option.isNone)
98
99
  if (Option.isNone(oexit)) {
99
100
  instance.suspended = true
100
101
  return yield* Effect.interrupt
@@ -112,6 +113,106 @@ export {
112
113
  await_ as await
113
114
  }
114
115
 
116
+ /**
117
+ * @since 1.0.0
118
+ * @category Combinators
119
+ */
120
+ export const into: {
121
+ /**
122
+ * @since 1.0.0
123
+ * @category Combinators
124
+ */
125
+ <Success extends Schema.Schema.Any, Error extends Schema.Schema.All>(self: DurableDeferred<Success, Error>): <R>(effect: Effect.Effect<Success["Type"], Error["Type"], R>) => Effect.Effect<
126
+ Success["Type"],
127
+ Error["Type"],
128
+ R | WorkflowEngine | WorkflowInstance | Success["Context"] | Error["Context"]
129
+ >
130
+ /**
131
+ * @since 1.0.0
132
+ * @category Combinators
133
+ */
134
+ <Success extends Schema.Schema.Any, Error extends Schema.Schema.All, R>(
135
+ effect: Effect.Effect<Success["Type"], Error["Type"], R>,
136
+ self: DurableDeferred<Success, Error>
137
+ ): Effect.Effect<
138
+ Success["Type"],
139
+ Error["Type"],
140
+ R | WorkflowEngine | WorkflowInstance | Success["Context"] | Error["Context"]
141
+ >
142
+ } = dual(2, <Success extends Schema.Schema.Any, Error extends Schema.Schema.All, R>(
143
+ effect: Effect.Effect<Success["Type"], Error["Type"], R>,
144
+ self: DurableDeferred<Success, Error>
145
+ ): Effect.Effect<
146
+ Success["Type"],
147
+ Error["Type"],
148
+ R | WorkflowEngine | WorkflowInstance | Success["Context"] | Error["Context"]
149
+ > =>
150
+ Effect.contextWithEffect((context: Context.Context<WorkflowEngine | WorkflowInstance>) => {
151
+ const engine = Context.get(context, EngineTag)
152
+ const instance = Context.get(context, InstanceTag)
153
+ return Effect.onExit(
154
+ effect,
155
+ Effect.fnUntraced(function*(exit) {
156
+ if (instance.suspended) return
157
+ const encodedExit = yield* Effect.orDie(Schema.encode(self.exitSchema)(exit))
158
+ yield* engine.deferredDone({
159
+ workflowName: instance.workflow.name,
160
+ executionId: instance.executionId,
161
+ deferred: self,
162
+ exit: encodedExit as any
163
+ })
164
+ })
165
+ )
166
+ }))
167
+
168
+ /**
169
+ * @since 1.0.0
170
+ * @category Racing
171
+ */
172
+ export const raceAll = <
173
+ const Effects extends NonEmptyReadonlyArray<Effect.Effect<any, any, any>>,
174
+ SI,
175
+ SR,
176
+ EI,
177
+ ER
178
+ >(options: {
179
+ name: string
180
+ success: Schema.Schema<
181
+ Effects[number] extends Effect.Effect<infer S, infer _E, infer _R> ? S : never,
182
+ SI,
183
+ SR
184
+ >
185
+ error: Schema.Schema<
186
+ Effects[number] extends Effect.Effect<infer _S, infer E, infer _R> ? E : never,
187
+ EI,
188
+ ER
189
+ >
190
+ effects: Effects
191
+ }): Effect.Effect<
192
+ (Effects[number] extends Effect.Effect<infer _A, infer _E, infer _R> ? _A : never),
193
+ (Effects[number] extends Effect.Effect<infer _A, infer _E, infer _R> ? _E : never),
194
+ | (Effects[number] extends Effect.Effect<infer _A, infer _R, infer R> ? R : never)
195
+ | SR
196
+ | ER
197
+ | WorkflowEngine
198
+ | WorkflowInstance
199
+ > => {
200
+ const deferred = make<any, any>(`raceAll/${options.name}`, {
201
+ success: options.success,
202
+ error: options.error
203
+ })
204
+ return Effect.gen(function*() {
205
+ const engine = yield* EngineTag
206
+ const oexit = yield* Workflow.wrapActivityResult(engine.deferredResult(deferred), Option.isNone)
207
+ if (Option.isSome(oexit)) {
208
+ return yield* (Effect.flatten(Effect.orDie(
209
+ Schema.decodeUnknown(deferred.exitSchema)(oexit.value)
210
+ )) as Effect.Effect<any, any, any>)
211
+ }
212
+ return yield* into(Effect.raceAll(options.effects), deferred)
213
+ })
214
+ }
215
+
115
216
  /**
116
217
  * @since 1.0.0
117
218
  * @category Token
package/src/Workflow.ts CHANGED
@@ -291,7 +291,6 @@ export const make = <
291
291
  if (result._tag === "Complete") {
292
292
  return yield* result.exit as Exit.Exit<Success["Type"], Error["Type"]>
293
293
  }
294
- // @effect-diagnostics effect/floatingEffect:off
295
294
  sleep ??= (yield* Schedule.driver(suspendedRetrySchedule)).next(void 0).pipe(
296
295
  Effect.catchAll(() => Effect.dieMessage(`${options.name}.execute: suspendedRetrySchedule exhausted`))
297
296
  )
@@ -480,18 +479,47 @@ export const Result = <Success extends Schema.Schema.Any, Error extends Schema.S
480
479
  */
481
480
  export const intoResult = <A, E, R>(
482
481
  effect: Effect.Effect<A, E, R>
483
- ): Effect.Effect<Result<A, E>, never, R | WorkflowInstance> =>
484
- Effect.uninterruptibleMask((restore) =>
485
- Effect.withFiberRuntime((fiber) =>
486
- Effect.matchCause(restore(effect), {
487
- onSuccess: (value) => new Complete({ exit: Exit.succeed(value) }),
488
- onFailure(cause) {
489
- const instance = Context.unsafeGet(fiber.currentContext, InstanceTag)
490
- return instance.suspended ? new Suspended() : new Complete({ exit: Exit.failCause(cause) })
491
- }
492
- })
482
+ ): Effect.Effect<Result<A, E>, never, Exclude<R, Scope.Scope> | WorkflowInstance> =>
483
+ Effect.contextWithEffect((context: Context.Context<WorkflowInstance>) => {
484
+ const instance = Context.get(context, InstanceTag)
485
+ return Effect.uninterruptibleMask((restore) =>
486
+ restore(effect).pipe(
487
+ Effect.scoped,
488
+ Effect.matchCause({
489
+ onSuccess: (value) => new Complete({ exit: Exit.succeed(value) }),
490
+ onFailure: (cause) => instance.suspended ? new Suspended() : new Complete({ exit: Exit.failCause(cause) })
491
+ })
492
+ )
493
493
  )
494
- )
494
+ })
495
+
496
+ /**
497
+ * @since 1.0.0
498
+ * @category Result
499
+ */
500
+ export const wrapActivityResult = <A, E, R>(
501
+ effect: Effect.Effect<A, E, R>,
502
+ isSuspend: (value: A) => boolean
503
+ ): Effect.Effect<A, E, R | WorkflowInstance> =>
504
+ Effect.contextWithEffect((context: Context.Context<WorkflowInstance>) => {
505
+ const instance = Context.get(context, InstanceTag)
506
+ const state = instance.activityState
507
+ if (instance.suspended) {
508
+ return state.count > 0 ?
509
+ state.latch.await.pipe(
510
+ Effect.andThen(Effect.yieldNow()),
511
+ Effect.andThen(Effect.interrupt)
512
+ ) :
513
+ Effect.interrupt
514
+ }
515
+ if (state.count === 0) state.latch.unsafeClose()
516
+ state.count++
517
+ return Effect.onExit(effect, (exit) => {
518
+ state.count--
519
+ const isSuspended = Exit.isSuccess(exit) && isSuspend(exit.value)
520
+ return state.count === 0 ? state.latch.open : isSuspended ? state.latch.await : Effect.void
521
+ })
522
+ })
495
523
 
496
524
  /**
497
525
  * Add compensation logic to an effect inside a Workflow. The compensation finalizer will be
@@ -541,14 +569,15 @@ export const withCompensation: {
541
569
  compensation: (value: A, cause: Cause.Cause<unknown>) => Effect.Effect<void, never, R2>
542
570
  ): Effect.Effect<A, E, R | R2 | WorkflowInstance | Scope.Scope> =>
543
571
  Effect.uninterruptibleMask((restore) =>
544
- Effect.contextWithEffect((context: Context.Context<WorkflowInstance>) => {
545
- const instance = Context.get(context, InstanceTag)
546
- return Effect.tap(restore(effect), (value) =>
547
- Effect.addFinalizer((exit) => {
548
- if (Exit.isSuccess(exit) || instance.suspended) {
549
- return Effect.void
550
- }
551
- return compensation(value, exit.cause)
552
- }))
553
- })
572
+ Effect.tap(
573
+ restore(effect),
574
+ (value) =>
575
+ Effect.contextWithEffect((context: Context.Context<WorkflowInstance>) =>
576
+ Effect.addFinalizer((exit) =>
577
+ Exit.isSuccess(exit) || Context.get(context, InstanceTag).suspended
578
+ ? Effect.void
579
+ : compensation(value, exit.cause)
580
+ )
581
+ )
582
+ )
554
583
  ))
@@ -2,7 +2,7 @@
2
2
  * @since 1.0.0
3
3
  */
4
4
  import * as Context from "effect/Context"
5
- import type * as Effect from "effect/Effect"
5
+ import * as Effect from "effect/Effect"
6
6
  import type * as Option from "effect/Option"
7
7
  import type * as Schema from "effect/Schema"
8
8
  import type * as Activity from "./Activity.js"
@@ -48,14 +48,6 @@ export class WorkflowEngine extends Context.Tag("@effect/workflow/WorkflowEngine
48
48
  executionId: string
49
49
  ) => Effect.Effect<void>
50
50
 
51
- /**
52
- * Resume a suspended workflow.
53
- */
54
- readonly resume: (
55
- workflowName: string,
56
- executionId: string
57
- ) => Effect.Effect<void>
58
-
59
51
  /**
60
52
  * Execute an activity from a workflow.
61
53
  */
@@ -118,5 +110,22 @@ export class WorkflowInstance extends Context.Tag("@effect/workflow/WorkflowEngi
118
110
  * Whether the workflow has requested to be suspended.
119
111
  */
120
112
  suspended: boolean
113
+
114
+ readonly activityState: {
115
+ count: number
116
+ readonly latch: Effect.Latch
117
+ }
121
118
  }
122
- >() {}
119
+ >() {
120
+ static initial(workflow: Workflow.Any, executionId: string): WorkflowInstance["Type"] {
121
+ return WorkflowInstance.of({
122
+ executionId,
123
+ workflow,
124
+ suspended: false,
125
+ activityState: {
126
+ count: 0,
127
+ latch: Effect.unsafeMakeLatch()
128
+ }
129
+ })
130
+ }
131
+ }