@effect/workflow 0.12.5 → 0.13.1

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 (49) hide show
  1. package/DurableQueue/package.json +6 -0
  2. package/dist/cjs/Activity.js +2 -6
  3. package/dist/cjs/Activity.js.map +1 -1
  4. package/dist/cjs/DurableClock.js +1 -2
  5. package/dist/cjs/DurableClock.js.map +1 -1
  6. package/dist/cjs/DurableDeferred.js +16 -18
  7. package/dist/cjs/DurableDeferred.js.map +1 -1
  8. package/dist/cjs/DurableQueue.js +186 -0
  9. package/dist/cjs/DurableQueue.js.map +1 -0
  10. package/dist/cjs/Workflow.js +8 -53
  11. package/dist/cjs/Workflow.js.map +1 -1
  12. package/dist/cjs/WorkflowEngine.js +234 -3
  13. package/dist/cjs/WorkflowEngine.js.map +1 -1
  14. package/dist/cjs/index.js +3 -1
  15. package/dist/dts/Activity.d.ts +1 -0
  16. package/dist/dts/Activity.d.ts.map +1 -1
  17. package/dist/dts/DurableClock.d.ts.map +1 -1
  18. package/dist/dts/DurableDeferred.d.ts +2 -2
  19. package/dist/dts/DurableDeferred.d.ts.map +1 -1
  20. package/dist/dts/DurableQueue.d.ts +116 -0
  21. package/dist/dts/DurableQueue.d.ts.map +1 -0
  22. package/dist/dts/Workflow.d.ts +2 -2
  23. package/dist/dts/Workflow.d.ts.map +1 -1
  24. package/dist/dts/WorkflowEngine.d.ts +59 -30
  25. package/dist/dts/WorkflowEngine.d.ts.map +1 -1
  26. package/dist/dts/index.d.ts +4 -0
  27. package/dist/dts/index.d.ts.map +1 -1
  28. package/dist/esm/Activity.js +2 -6
  29. package/dist/esm/Activity.js.map +1 -1
  30. package/dist/esm/DurableClock.js +1 -2
  31. package/dist/esm/DurableClock.js.map +1 -1
  32. package/dist/esm/DurableDeferred.js +16 -18
  33. package/dist/esm/DurableDeferred.js.map +1 -1
  34. package/dist/esm/DurableQueue.js +176 -0
  35. package/dist/esm/DurableQueue.js.map +1 -0
  36. package/dist/esm/Workflow.js +7 -52
  37. package/dist/esm/Workflow.js.map +1 -1
  38. package/dist/esm/WorkflowEngine.js +232 -2
  39. package/dist/esm/WorkflowEngine.js.map +1 -1
  40. package/dist/esm/index.js +4 -0
  41. package/dist/esm/index.js.map +1 -1
  42. package/package.json +13 -5
  43. package/src/Activity.ts +3 -8
  44. package/src/DurableClock.ts +1 -2
  45. package/src/DurableDeferred.ts +24 -30
  46. package/src/DurableQueue.ts +332 -0
  47. package/src/Workflow.ts +9 -61
  48. package/src/WorkflowEngine.ts +509 -44
  49. package/src/index.ts +5 -0
@@ -4,16 +4,21 @@
4
4
  import type * as Cause from "effect/Cause"
5
5
  import * as Context from "effect/Context"
6
6
  import * as Effect from "effect/Effect"
7
- import type * as Option from "effect/Option"
8
- import type * as Schema from "effect/Schema"
7
+ import * as Exit from "effect/Exit"
8
+ import * as Fiber from "effect/Fiber"
9
+ import * as FiberMap from "effect/FiberMap"
10
+ import * as Layer from "effect/Layer"
11
+ import * as Option from "effect/Option"
12
+ import * as Schedule from "effect/Schedule"
13
+ import * as Schema from "effect/Schema"
9
14
  import type * as Scope from "effect/Scope"
10
15
  import type * as Activity from "./Activity.js"
11
16
  import type { DurableClock } from "./DurableClock.js"
12
17
  import type * as DurableDeferred from "./DurableDeferred.js"
13
- import type * as Workflow from "./Workflow.js"
18
+ import * as Workflow from "./Workflow.js"
14
19
 
15
20
  /**
16
- * @since 1.0.0
21
+ * @since 4.0.0
17
22
  * @category Services
18
23
  */
19
24
  export class WorkflowEngine extends Context.Tag("@effect/workflow/WorkflowEngine")<
@@ -22,39 +27,77 @@ export class WorkflowEngine extends Context.Tag("@effect/workflow/WorkflowEngine
22
27
  /**
23
28
  * Register a workflow with the engine.
24
29
  */
25
- readonly register: (
26
- workflow: Workflow.Any,
30
+ readonly register: <
31
+ Name extends string,
32
+ Payload extends Workflow.AnyStructSchema,
33
+ Success extends Schema.Schema.Any,
34
+ Error extends Schema.Schema.All,
35
+ R
36
+ >(
37
+ workflow: Workflow.Workflow<Name, Payload, Success, Error>,
27
38
  execute: (
28
- payload: object,
39
+ payload: Payload["Type"],
29
40
  executionId: string
30
- ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>
31
- ) => Effect.Effect<void, never, Scope.Scope>
41
+ ) => Effect.Effect<Success["Type"], Error["Type"], R>
42
+ ) => Effect.Effect<
43
+ void,
44
+ never,
45
+ | Scope.Scope
46
+ | Exclude<
47
+ R,
48
+ | WorkflowEngine
49
+ | WorkflowInstance
50
+ | Workflow.Execution<Name>
51
+ | Scope.Scope
52
+ >
53
+ | Payload["Context"]
54
+ | Success["Context"]
55
+ | Error["Context"]
56
+ >
32
57
 
33
58
  /**
34
59
  * Execute a registered workflow.
35
60
  */
36
- readonly execute: <const Discard extends boolean>(
61
+ readonly execute: <
62
+ Name extends string,
63
+ Payload extends Workflow.AnyStructSchema,
64
+ Success extends Schema.Schema.Any,
65
+ Error extends Schema.Schema.All,
66
+ const Discard extends boolean = false
67
+ >(
68
+ workflow: Workflow.Workflow<Name, Payload, Success, Error>,
37
69
  options: {
38
- readonly workflow: Workflow.Any
39
70
  readonly executionId: string
40
- readonly payload: object
41
- readonly discard: Discard
42
- readonly parent?: WorkflowInstance["Type"] | undefined
71
+ readonly payload: Payload["Type"]
72
+ readonly discard?: Discard | undefined
73
+ readonly suspendedRetrySchedule?:
74
+ | Schedule.Schedule<any, unknown>
75
+ | undefined
43
76
  }
44
- ) => Effect.Effect<Discard extends true ? void : Workflow.Result<unknown, unknown>>
77
+ ) => Effect.Effect<
78
+ Discard extends true ? string : Success["Type"],
79
+ Error["Type"],
80
+ | Payload["Context"]
81
+ | Success["Context"]
82
+ | Error["Context"]
83
+ >
45
84
 
46
85
  /**
47
- * Poll a registered workflow for its current status.
48
- *
49
- * If the workflow has not run yet, it will return `undefined`, otherwise it
50
- * will return the current `Workflow.Result`.
86
+ * Execute a registered workflow.
51
87
  */
52
- readonly poll: (
53
- options: {
54
- readonly workflow: Workflow.Any
55
- readonly executionId: string
56
- }
57
- ) => Effect.Effect<Workflow.Result<unknown, unknown> | undefined>
88
+ readonly poll: <
89
+ Name extends string,
90
+ Payload extends Workflow.AnyStructSchema,
91
+ Success extends Schema.Schema.Any,
92
+ Error extends Schema.Schema.All
93
+ >(
94
+ workflow: Workflow.Workflow<Name, Payload, Success, Error>,
95
+ executionId: string
96
+ ) => Effect.Effect<
97
+ Workflow.Result<Success["Type"], Error["Type"]> | undefined,
98
+ never,
99
+ Success["Context"] | Error["Context"]
100
+ >
58
101
 
59
102
  /**
60
103
  * Interrupt a registered workflow.
@@ -75,46 +118,72 @@ export class WorkflowEngine extends Context.Tag("@effect/workflow/WorkflowEngine
75
118
  /**
76
119
  * Execute an activity from a workflow.
77
120
  */
78
- readonly activityExecute: (
79
- options: {
80
- readonly activity: Activity.Any
81
- readonly attempt: number
82
- }
83
- ) => Effect.Effect<Workflow.Result<unknown, unknown>, never, WorkflowInstance>
121
+ readonly activityExecute: <
122
+ Success extends Schema.Schema.Any,
123
+ Error extends Schema.Schema.All,
124
+ R
125
+ >(
126
+ activity: Activity.Activity<Success, Error, R>,
127
+ attempt: number
128
+ ) => Effect.Effect<
129
+ Workflow.Result<Success["Type"], Error["Type"]>,
130
+ never,
131
+ | Success["Context"]
132
+ | Error["Context"]
133
+ | R
134
+ | WorkflowInstance
135
+ >
84
136
 
85
137
  /**
86
138
  * Try to retrieve the result of an DurableDeferred
87
139
  */
88
- readonly deferredResult: (
89
- deferred: DurableDeferred.Any
90
- ) => Effect.Effect<Option.Option<Schema.ExitEncoded<unknown, unknown, unknown>>, never, WorkflowInstance>
140
+ readonly deferredResult: <
141
+ Success extends Schema.Schema.Any,
142
+ Error extends Schema.Schema.All
143
+ >(
144
+ deferred: DurableDeferred.DurableDeferred<Success, Error>
145
+ ) => Effect.Effect<
146
+ Exit.Exit<Success["Type"], Error["Type"]> | undefined,
147
+ never,
148
+ WorkflowInstance
149
+ >
91
150
 
92
151
  /**
93
152
  * Set the result of a DurableDeferred, and then resume any waiting
94
153
  * workflows.
95
154
  */
96
- readonly deferredDone: (
155
+ readonly deferredDone: <
156
+ Success extends Schema.Schema.Any,
157
+ Error extends Schema.Schema.All
158
+ >(
159
+ deferred: DurableDeferred.DurableDeferred<Success, Error>,
97
160
  options: {
98
161
  readonly workflowName: string
99
162
  readonly executionId: string
100
163
  readonly deferredName: string
101
- readonly exit: Schema.ExitEncoded<unknown, unknown, unknown>
164
+ readonly exit: Exit.Exit<Success["Type"], Error["Type"]>
102
165
  }
103
- ) => Effect.Effect<void>
166
+ ) => Effect.Effect<
167
+ void,
168
+ never,
169
+ Success["Context"] | Error["Context"]
170
+ >
104
171
 
105
172
  /**
106
173
  * Schedule a wake up for a DurableClock
107
174
  */
108
- readonly scheduleClock: (options: {
109
- readonly workflow: Workflow.Any
110
- readonly executionId: string
111
- readonly clock: DurableClock
112
- }) => Effect.Effect<void>
175
+ readonly scheduleClock: (
176
+ workflow: Workflow.Any,
177
+ options: {
178
+ readonly executionId: string
179
+ readonly clock: DurableClock
180
+ }
181
+ ) => Effect.Effect<void>
113
182
  }
114
183
  >() {}
115
184
 
116
185
  /**
117
- * @since 1.0.0
186
+ * @since 4.0.0
118
187
  * @category Services
119
188
  */
120
189
  export class WorkflowInstance extends Context.Tag("@effect/workflow/WorkflowEngine/WorkflowInstance")<
@@ -152,7 +221,10 @@ export class WorkflowInstance extends Context.Tag("@effect/workflow/WorkflowEngi
152
221
  }
153
222
  }
154
223
  >() {
155
- static initial(workflow: Workflow.Any, executionId: string): WorkflowInstance["Type"] {
224
+ static initial(
225
+ workflow: Workflow.Any,
226
+ executionId: string
227
+ ): WorkflowInstance["Type"] {
156
228
  return WorkflowInstance.of({
157
229
  executionId,
158
230
  workflow,
@@ -166,3 +238,396 @@ export class WorkflowInstance extends Context.Tag("@effect/workflow/WorkflowEngi
166
238
  })
167
239
  }
168
240
  }
241
+
242
+ /**
243
+ * @since 4.0.0
244
+ * @category Encoded
245
+ */
246
+ export interface Encoded {
247
+ readonly register: (
248
+ workflow: Workflow.Any,
249
+ execute: (
250
+ payload: object,
251
+ executionId: string
252
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>
253
+ ) => Effect.Effect<void, never, Scope.Scope>
254
+ readonly execute: <const Discard extends boolean>(
255
+ workflow: Workflow.Any,
256
+ options: {
257
+ readonly executionId: string
258
+ readonly payload: object
259
+ readonly discard: Discard
260
+ readonly parent?: WorkflowInstance["Type"] | undefined
261
+ }
262
+ ) => Effect.Effect<
263
+ Discard extends true ? void : Workflow.Result<unknown, unknown>
264
+ >
265
+ readonly poll: (
266
+ workflow: Workflow.Any,
267
+ executionId: string
268
+ ) => Effect.Effect<Workflow.Result<unknown, unknown> | undefined>
269
+ readonly interrupt: (
270
+ workflow: Workflow.Any,
271
+ executionId: string
272
+ ) => Effect.Effect<void>
273
+ readonly resume: (
274
+ workflow: Workflow.Any,
275
+ executionId: string
276
+ ) => Effect.Effect<void>
277
+ readonly activityExecute: (
278
+ activity: Activity.Any,
279
+ attempt: number
280
+ ) => Effect.Effect<
281
+ Workflow.Result<unknown, unknown>,
282
+ never,
283
+ WorkflowInstance
284
+ >
285
+ readonly deferredResult: (
286
+ deferred: DurableDeferred.Any
287
+ ) => Effect.Effect<
288
+ Exit.Exit<unknown, unknown> | undefined,
289
+ never,
290
+ WorkflowInstance
291
+ >
292
+ readonly deferredDone: (options: {
293
+ readonly workflowName: string
294
+ readonly executionId: string
295
+ readonly deferredName: string
296
+ readonly exit: Exit.Exit<unknown, unknown>
297
+ }) => Effect.Effect<void>
298
+ readonly scheduleClock: (
299
+ workflow: Workflow.Any,
300
+ options: {
301
+ readonly executionId: string
302
+ readonly clock: DurableClock
303
+ }
304
+ ) => Effect.Effect<void>
305
+ }
306
+
307
+ /**
308
+ * @since 4.0.0
309
+ * @category Constructors
310
+ */
311
+ export const makeUnsafe = (options: Encoded): WorkflowEngine["Type"] =>
312
+ WorkflowEngine.of({
313
+ register: Effect.fnUntraced(function*(workflow, execute) {
314
+ const context = yield* Effect.context<WorkflowEngine>()
315
+ yield* options.register(workflow, (payload, executionId) =>
316
+ Effect.suspend(() =>
317
+ execute(payload, executionId)
318
+ ).pipe(
319
+ Effect.mapInputContext(
320
+ (input) => Context.merge(context, input) as Context.Context<any>
321
+ )
322
+ ))
323
+ }),
324
+ execute: Effect.fnUntraced(function*<
325
+ Name extends string,
326
+ Payload extends Workflow.AnyStructSchema,
327
+ Success extends Schema.Schema.Any,
328
+ Error extends Schema.Schema.All,
329
+ const Discard extends boolean = false
330
+ >(
331
+ self: Workflow.Workflow<Name, Payload, Success, Error>,
332
+ opts: {
333
+ readonly executionId: string
334
+ readonly payload: Payload["Type"]
335
+ readonly discard?: Discard | undefined
336
+ readonly suspendedRetrySchedule?:
337
+ | Schedule.Schedule<any, unknown>
338
+ | undefined
339
+ }
340
+ ) {
341
+ const payload = opts.payload
342
+ const executionId = opts.executionId
343
+ const suspendedRetrySchedule = opts.suspendedRetrySchedule ?? defaultRetrySchedule
344
+ yield* Effect.annotateCurrentSpan({ executionId })
345
+ let result: Workflow.Result<Success["Type"], Error["Type"]> | undefined
346
+
347
+ // link interruption with parent workflow
348
+ const parentInstance = yield* Effect.serviceOption(WorkflowInstance)
349
+ if (Option.isSome(parentInstance)) {
350
+ const instance = parentInstance.value
351
+ yield* Effect.addFinalizer(() => {
352
+ if (!instance.interrupted || result?._tag === "Complete") {
353
+ return Effect.void
354
+ }
355
+ return options.interrupt(self, executionId)
356
+ })
357
+ }
358
+
359
+ if (opts.discard) {
360
+ yield* options.execute(self, {
361
+ executionId,
362
+ payload: payload as object,
363
+ discard: true
364
+ })
365
+ return executionId
366
+ }
367
+
368
+ const run = options.execute(self, {
369
+ executionId,
370
+ payload: payload as object,
371
+ discard: false,
372
+ parent: Option.getOrUndefined(parentInstance)
373
+ })
374
+ if (Option.isSome(parentInstance)) {
375
+ result = yield* Workflow.wrapActivityResult(
376
+ run,
377
+ (result) => result._tag === "Suspended"
378
+ )
379
+ if (result._tag === "Suspended") {
380
+ return yield* Workflow.suspend(parentInstance.value)
381
+ }
382
+ return yield* result.exit
383
+ }
384
+
385
+ let sleep: Effect.Effect<any> | undefined
386
+ while (true) {
387
+ result = yield* run
388
+ if (result._tag === "Complete") {
389
+ return yield* result.exit as Exit.Exit<any>
390
+ }
391
+ sleep ??= (yield* Schedule.driver(suspendedRetrySchedule)).next(void 0).pipe(
392
+ Effect.catchAll(() => Effect.dieMessage(`${self.name}.execute: suspendedRetrySchedule exhausted`))
393
+ )
394
+ yield* sleep
395
+ }
396
+ }),
397
+ poll: options.poll,
398
+ interrupt: options.interrupt,
399
+ resume: options.resume,
400
+ activityExecute: Effect.fnUntraced(function*<
401
+ Success extends Schema.Schema.Any,
402
+ Error extends Schema.Schema.All,
403
+ R
404
+ >(activity: Activity.Activity<Success, Error, R>, attempt: number) {
405
+ const result = yield* options.activityExecute(activity, attempt)
406
+ if (result._tag === "Suspended") {
407
+ return result
408
+ }
409
+ const exit = yield* Effect.orDie(
410
+ Schema.decode(activity.exitSchema)(result.exit)
411
+ )
412
+ return new Workflow.Complete({ exit })
413
+ }),
414
+ deferredResult: Effect.fnUntraced(
415
+ function*<Success extends Schema.Schema.Any, Error extends Schema.Schema.All>(
416
+ deferred: DurableDeferred.DurableDeferred<Success, Error>
417
+ ) {
418
+ const instance = yield* WorkflowInstance
419
+ yield* Effect.annotateCurrentSpan({
420
+ executionId: instance.executionId
421
+ })
422
+ const exit = yield* options.deferredResult(deferred)
423
+ if (exit === undefined) {
424
+ return exit
425
+ }
426
+ return yield* Effect.orDie(
427
+ Schema.decodeUnknown(deferred.exitSchema)(exit)
428
+ ) as Effect.Effect<Exit.Exit<Success["Type"], Error["Type"]>>
429
+ }
430
+ ),
431
+ deferredDone: Effect.fnUntraced(
432
+ function*<Success extends Schema.Schema.Any, Error extends Schema.Schema.All>(
433
+ deferred: DurableDeferred.DurableDeferred<Success, Error>,
434
+ opts: {
435
+ readonly workflowName: string
436
+ readonly executionId: string
437
+ readonly deferredName: string
438
+ readonly exit: Exit.Exit<Success["Type"], Error["Type"]>
439
+ }
440
+ ) {
441
+ return yield* options.deferredDone({
442
+ workflowName: opts.workflowName,
443
+ executionId: opts.executionId,
444
+ deferredName: opts.deferredName,
445
+ exit: yield* Schema.encode(deferred.exitSchema)(
446
+ opts.exit
447
+ ) as Effect.Effect<Exit.Exit<unknown, unknown>>
448
+ })
449
+ }
450
+ ),
451
+ scheduleClock: options.scheduleClock
452
+ })
453
+
454
+ const defaultRetrySchedule = Schedule.exponential(200, 1.5).pipe(
455
+ Schedule.either(Schedule.spaced(30000))
456
+ )
457
+
458
+ /**
459
+ * @since 1.0.0
460
+ * @category In-memory
461
+ */
462
+ export const layerMemory: Layer.Layer<WorkflowEngine> = Layer.scoped(
463
+ WorkflowEngine,
464
+ Effect.gen(function*() {
465
+ const scope = yield* Effect.scope
466
+
467
+ const workflows = new Map<string, {
468
+ readonly workflow: Workflow.Any
469
+ readonly execute: (
470
+ payload: object,
471
+ executionId: string
472
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>
473
+ readonly scope: Scope.Scope
474
+ }>()
475
+
476
+ type ExecutionState = {
477
+ readonly payload: object
478
+ readonly execute: (
479
+ payload: object,
480
+ executionId: string
481
+ ) => Effect.Effect<unknown, unknown, WorkflowInstance | WorkflowEngine>
482
+ readonly parent: string | undefined
483
+ instance: WorkflowInstance["Type"]
484
+ fiber: Fiber.RuntimeFiber<Workflow.Result<unknown, unknown>> | undefined
485
+ resumeLatch?: Effect.Latch
486
+ }
487
+ const executions = new Map<string, ExecutionState>()
488
+
489
+ type ActivityState = {
490
+ exit: Exit.Exit<Workflow.Result<unknown, unknown>> | undefined
491
+ }
492
+ const activities = new Map<string, ActivityState>()
493
+
494
+ const resume = Effect.fnUntraced(function*(executionId: string): Effect.fn.Return<void> {
495
+ const state = executions.get(executionId)
496
+ if (!state) return
497
+ const exit = state.fiber?.unsafePoll()
498
+ if (exit && exit._tag === "Success" && exit.value._tag === "Complete") {
499
+ return
500
+ } else if (state.fiber && !exit) {
501
+ return
502
+ }
503
+
504
+ const entry = workflows.get(state.instance.workflow.name)!
505
+ const instance = WorkflowInstance.initial(state.instance.workflow, state.instance.executionId)
506
+ instance.interrupted = state.instance.interrupted
507
+ state.instance = instance
508
+ state.fiber = yield* state.execute(state.payload, state.instance.executionId).pipe(
509
+ Effect.onExit(() => {
510
+ if (!instance.interrupted) {
511
+ return Effect.void
512
+ }
513
+ instance.suspended = false
514
+ return Effect.withFiberRuntime<void>((fiber) => Effect.interruptible(Fiber.interrupt(fiber)))
515
+ }),
516
+ Workflow.intoResult,
517
+ Effect.provideService(WorkflowInstance, instance),
518
+ Effect.provideService(WorkflowEngine, engine),
519
+ Effect.tap((result) => {
520
+ if (!state.parent || result._tag !== "Complete") {
521
+ return Effect.void
522
+ }
523
+ return Effect.forkIn(resume(state.parent), scope)
524
+ }),
525
+ Effect.forkIn(entry.scope)
526
+ )
527
+ })
528
+
529
+ const deferredResults = new Map<string, Exit.Exit<any, any>>()
530
+
531
+ const clocks = yield* FiberMap.make()
532
+
533
+ const engine = makeUnsafe({
534
+ register: Effect.fnUntraced(function*(workflow, execute) {
535
+ workflows.set(workflow.name, {
536
+ workflow,
537
+ execute,
538
+ scope: yield* Effect.scope
539
+ })
540
+ }),
541
+ execute: Effect.fnUntraced(function*(workflow, options) {
542
+ const entry = workflows.get(workflow.name)
543
+ if (!entry) {
544
+ return yield* Effect.die(`Workflow ${workflow.name} is not registered`)
545
+ }
546
+
547
+ let state = executions.get(options.executionId)
548
+ if (!state) {
549
+ state = {
550
+ payload: options.payload,
551
+ execute: entry.execute,
552
+ instance: WorkflowInstance.initial(workflow, options.executionId),
553
+ fiber: undefined,
554
+ parent: options.parent?.executionId
555
+ }
556
+ executions.set(options.executionId, state)
557
+ yield* resume(options.executionId)
558
+ }
559
+ if (options.discard) return
560
+ return (yield* Fiber.join(state.fiber!)) as any
561
+ }),
562
+ interrupt: Effect.fnUntraced(function*(_workflow, executionId) {
563
+ const state = executions.get(executionId)
564
+ if (!state) return
565
+ state.instance.interrupted = true
566
+ yield* resume(executionId)
567
+ }),
568
+ resume(_workflow, executionId) {
569
+ return resume(executionId)
570
+ },
571
+ activityExecute: Effect.fnUntraced(function*(activity, attempt) {
572
+ const instance = yield* WorkflowInstance
573
+ const activityId = `${instance.executionId}/${activity.name}/${attempt}`
574
+ let state = activities.get(activityId)
575
+ if (state) {
576
+ const exit = state.exit
577
+ if (exit && exit._tag === "Success" && exit.value._tag === "Suspended") {
578
+ state.exit = undefined
579
+ } else if (exit) {
580
+ return yield* exit
581
+ }
582
+ } else {
583
+ state = { exit: undefined }
584
+ activities.set(activityId, state)
585
+ }
586
+ const activityInstance = WorkflowInstance.initial(instance.workflow, instance.executionId)
587
+ activityInstance.interrupted = instance.interrupted
588
+ return yield* activity.executeEncoded.pipe(
589
+ Workflow.intoResult,
590
+ Effect.provideService(WorkflowInstance, activityInstance),
591
+ Effect.onExit((exit) => {
592
+ state.exit = exit
593
+ return Effect.void
594
+ })
595
+ )
596
+ }),
597
+ poll: (_workflow, executionId) =>
598
+ Effect.suspend(() => {
599
+ const state = executions.get(executionId)
600
+ if (!state) {
601
+ return Effect.succeed(undefined)
602
+ }
603
+ const exit = state.fiber?.unsafePoll()
604
+ return exit ?? Effect.succeed(undefined)
605
+ }),
606
+ deferredResult: Effect.fnUntraced(function*(deferred) {
607
+ const instance = yield* WorkflowInstance
608
+ const id = `${instance.executionId}/${deferred.name}`
609
+ return deferredResults.get(id)
610
+ }),
611
+ deferredDone: (options) =>
612
+ Effect.suspend(() => {
613
+ const id = `${options.executionId}/${options.deferredName}`
614
+ if (deferredResults.has(id)) return Effect.void
615
+ deferredResults.set(id, options.exit)
616
+ return resume(options.executionId)
617
+ }),
618
+ scheduleClock: (workflow, options) =>
619
+ engine.deferredDone(options.clock.deferred, {
620
+ workflowName: workflow.name,
621
+ executionId: options.executionId,
622
+ deferredName: options.clock.deferred.name,
623
+ exit: Exit.void
624
+ }).pipe(
625
+ Effect.delay(options.clock.duration),
626
+ FiberMap.run(clocks, `${options.executionId}/${options.clock.name}`, { onlyIfMissing: true }),
627
+ Effect.asVoid
628
+ )
629
+ })
630
+
631
+ return engine
632
+ })
633
+ )
package/src/index.ts CHANGED
@@ -13,6 +13,11 @@ export * as DurableClock from "./DurableClock.js"
13
13
  */
14
14
  export * as DurableDeferred from "./DurableDeferred.js"
15
15
 
16
+ /**
17
+ * @since 1.0.0
18
+ */
19
+ export * as DurableQueue from "./DurableQueue.js"
20
+
16
21
  /**
17
22
  * @since 1.0.0
18
23
  */