@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.
- package/DurableQueue/package.json +6 -0
- package/dist/cjs/Activity.js +2 -6
- package/dist/cjs/Activity.js.map +1 -1
- package/dist/cjs/DurableClock.js +1 -2
- package/dist/cjs/DurableClock.js.map +1 -1
- package/dist/cjs/DurableDeferred.js +16 -18
- package/dist/cjs/DurableDeferred.js.map +1 -1
- package/dist/cjs/DurableQueue.js +186 -0
- package/dist/cjs/DurableQueue.js.map +1 -0
- package/dist/cjs/Workflow.js +8 -53
- package/dist/cjs/Workflow.js.map +1 -1
- package/dist/cjs/WorkflowEngine.js +234 -3
- package/dist/cjs/WorkflowEngine.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/dts/Activity.d.ts +1 -0
- package/dist/dts/Activity.d.ts.map +1 -1
- package/dist/dts/DurableClock.d.ts.map +1 -1
- package/dist/dts/DurableDeferred.d.ts +2 -2
- package/dist/dts/DurableDeferred.d.ts.map +1 -1
- package/dist/dts/DurableQueue.d.ts +116 -0
- package/dist/dts/DurableQueue.d.ts.map +1 -0
- package/dist/dts/Workflow.d.ts +2 -2
- package/dist/dts/Workflow.d.ts.map +1 -1
- package/dist/dts/WorkflowEngine.d.ts +59 -30
- package/dist/dts/WorkflowEngine.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Activity.js +2 -6
- package/dist/esm/Activity.js.map +1 -1
- package/dist/esm/DurableClock.js +1 -2
- package/dist/esm/DurableClock.js.map +1 -1
- package/dist/esm/DurableDeferred.js +16 -18
- package/dist/esm/DurableDeferred.js.map +1 -1
- package/dist/esm/DurableQueue.js +176 -0
- package/dist/esm/DurableQueue.js.map +1 -0
- package/dist/esm/Workflow.js +7 -52
- package/dist/esm/Workflow.js.map +1 -1
- package/dist/esm/WorkflowEngine.js +232 -2
- package/dist/esm/WorkflowEngine.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +13 -5
- package/src/Activity.ts +3 -8
- package/src/DurableClock.ts +1 -2
- package/src/DurableDeferred.ts +24 -30
- package/src/DurableQueue.ts +332 -0
- package/src/Workflow.ts +9 -61
- package/src/WorkflowEngine.ts +509 -44
- package/src/index.ts +5 -0
package/src/DurableDeferred.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as Effect from "effect/Effect"
|
|
|
9
9
|
import * as Encoding from "effect/Encoding"
|
|
10
10
|
import * as Exit from "effect/Exit"
|
|
11
11
|
import { dual } from "effect/Function"
|
|
12
|
-
import * as
|
|
12
|
+
import * as Predicate from "effect/Predicate"
|
|
13
13
|
import * as Schema from "effect/Schema"
|
|
14
14
|
import type * as Activity from "./Activity.js"
|
|
15
15
|
import * as Workflow from "./Workflow.js"
|
|
@@ -39,7 +39,7 @@ export interface DurableDeferred<
|
|
|
39
39
|
readonly name: string
|
|
40
40
|
readonly successSchema: Success
|
|
41
41
|
readonly errorSchema: Error
|
|
42
|
-
readonly exitSchema: Schema.
|
|
42
|
+
readonly exitSchema: Schema.ExitFromSelf<Success, Error, typeof Schema.Defect>
|
|
43
43
|
readonly withActivityAttempt: Effect.Effect<DurableDeferred<Success, Error>>
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -52,7 +52,7 @@ export interface Any {
|
|
|
52
52
|
readonly name: string
|
|
53
53
|
readonly successSchema: Schema.Schema.Any
|
|
54
54
|
readonly errorSchema: Schema.Schema.All
|
|
55
|
-
readonly exitSchema: Schema.
|
|
55
|
+
readonly exitSchema: Schema.ExitFromSelf<any, any, any>
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -70,7 +70,7 @@ export const make = <
|
|
|
70
70
|
name,
|
|
71
71
|
successSchema: options?.success ?? Schema.Void as any,
|
|
72
72
|
errorSchema: options?.error ?? Schema.Never as any,
|
|
73
|
-
exitSchema: Schema.
|
|
73
|
+
exitSchema: Schema.ExitFromSelf({
|
|
74
74
|
success: options?.success ?? Schema.Void as any,
|
|
75
75
|
failure: options?.error ?? Schema.Never as any,
|
|
76
76
|
defect: Schema.Defect
|
|
@@ -111,13 +111,14 @@ const await_: <Success extends Schema.Schema.Any, Error extends Schema.Schema.Al
|
|
|
111
111
|
>(self: DurableDeferred<Success, Error>) {
|
|
112
112
|
const engine = yield* EngineTag
|
|
113
113
|
const instance = yield* InstanceTag
|
|
114
|
-
const
|
|
115
|
-
|
|
114
|
+
const exit = yield* Workflow.wrapActivityResult(
|
|
115
|
+
engine.deferredResult(self),
|
|
116
|
+
Predicate.isUndefined
|
|
117
|
+
)
|
|
118
|
+
if (exit === undefined) {
|
|
116
119
|
return yield* Workflow.suspend(instance)
|
|
117
120
|
}
|
|
118
|
-
return yield*
|
|
119
|
-
Schema.decodeUnknown(self.exitSchema)(oexit.value)
|
|
120
|
-
))
|
|
121
|
+
return yield* exit
|
|
121
122
|
})
|
|
122
123
|
|
|
123
124
|
export {
|
|
@@ -165,19 +166,15 @@ export const into: {
|
|
|
165
166
|
Effect.contextWithEffect((context: Context.Context<WorkflowEngine | WorkflowInstance>) => {
|
|
166
167
|
const engine = Context.get(context, EngineTag)
|
|
167
168
|
const instance = Context.get(context, InstanceTag)
|
|
168
|
-
return Effect.onExit(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
executionId: instance.executionId,
|
|
176
|
-
deferredName: self.name,
|
|
177
|
-
exit: encodedExit as any
|
|
178
|
-
})
|
|
169
|
+
return Effect.onExit(effect, (exit) => {
|
|
170
|
+
if (instance.suspended) return Effect.void
|
|
171
|
+
return engine.deferredDone(self, {
|
|
172
|
+
workflowName: instance.workflow.name,
|
|
173
|
+
executionId: instance.executionId,
|
|
174
|
+
deferredName: self.name,
|
|
175
|
+
exit
|
|
179
176
|
})
|
|
180
|
-
)
|
|
177
|
+
})
|
|
181
178
|
}))
|
|
182
179
|
|
|
183
180
|
/**
|
|
@@ -218,11 +215,9 @@ export const raceAll = <
|
|
|
218
215
|
})
|
|
219
216
|
return Effect.gen(function*() {
|
|
220
217
|
const engine = yield* EngineTag
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
return yield* (Effect.flatten(Effect.
|
|
224
|
-
Schema.decodeUnknown(deferred.exitSchema)(oexit.value)
|
|
225
|
-
)) as Effect.Effect<any, any, any>)
|
|
218
|
+
const exit = yield* Workflow.wrapActivityResult(engine.deferredResult(deferred), Predicate.isUndefined)
|
|
219
|
+
if (exit) {
|
|
220
|
+
return yield* (Effect.flatten(exit) as Effect.Effect<any, any, any>)
|
|
226
221
|
}
|
|
227
222
|
return yield* into(Effect.raceAll(options.effects), deferred)
|
|
228
223
|
})
|
|
@@ -443,14 +438,13 @@ export const done: {
|
|
|
443
438
|
) {
|
|
444
439
|
const engine = yield* EngineTag
|
|
445
440
|
const token = TokenParsed.fromString(options.token)
|
|
446
|
-
|
|
447
|
-
yield* engine.deferredDone({
|
|
441
|
+
yield* engine.deferredDone(self, {
|
|
448
442
|
workflowName: token.workflowName,
|
|
449
443
|
executionId: token.executionId,
|
|
450
444
|
deferredName: token.deferredName,
|
|
451
|
-
exit: exit
|
|
445
|
+
exit: options.exit
|
|
452
446
|
})
|
|
453
|
-
}
|
|
447
|
+
})
|
|
454
448
|
)
|
|
455
449
|
|
|
456
450
|
/**
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as PersistedQueue from "@effect/experimental/PersistedQueue"
|
|
5
|
+
import * as Context from "effect/Context"
|
|
6
|
+
import * as Effect from "effect/Effect"
|
|
7
|
+
import * as Layer from "effect/Layer"
|
|
8
|
+
import * as Schedule from "effect/Schedule"
|
|
9
|
+
import * as Schema from "effect/Schema"
|
|
10
|
+
import * as Activity from "./Activity.js"
|
|
11
|
+
import * as DurableDeferred from "./DurableDeferred.js"
|
|
12
|
+
import { makeHashDigest } from "./internal/crypto.js"
|
|
13
|
+
import type * as WorkflowEngine from "./WorkflowEngine.js"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @since 1.0.0
|
|
17
|
+
* @category Type IDs
|
|
18
|
+
*/
|
|
19
|
+
export type TypeId = "~@effect/workflow/DurableQueue"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @since 1.0.0
|
|
23
|
+
* @category Type IDs
|
|
24
|
+
*/
|
|
25
|
+
export const TypeId: TypeId = "~@effect/workflow/DurableQueue"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @since 1.0.0
|
|
29
|
+
* @category Models
|
|
30
|
+
*/
|
|
31
|
+
export interface DurableQueue<
|
|
32
|
+
Payload extends Schema.Schema.Any,
|
|
33
|
+
Success extends Schema.Schema.Any = typeof Schema.Void,
|
|
34
|
+
Error extends Schema.Schema.All = typeof Schema.Never
|
|
35
|
+
> {
|
|
36
|
+
readonly [TypeId]: TypeId
|
|
37
|
+
readonly name: string
|
|
38
|
+
readonly payloadSchema: Payload
|
|
39
|
+
readonly idempotencyKey: (payload: Payload["Type"]) => string
|
|
40
|
+
readonly deferred: DurableDeferred.DurableDeferred<Success, Error>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A `DurableQueue` wraps a `PersistedQueue`, providing a way to wait for items
|
|
45
|
+
* to finish processing using a `DurableDeferred`.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { DurableQueue, Workflow } from "@effect/workflow"
|
|
49
|
+
* import { Effect, Schema } from "effect"
|
|
50
|
+
*
|
|
51
|
+
* // Define a DurableQueue that can be used to derive workers and offer items for
|
|
52
|
+
* // processing.
|
|
53
|
+
* const ApiQueue = DurableQueue.make({
|
|
54
|
+
* name: "ApiQueue",
|
|
55
|
+
* payload: {
|
|
56
|
+
* id: Schema.String
|
|
57
|
+
* },
|
|
58
|
+
* success: Schema.Void,
|
|
59
|
+
* error: Schema.Never,
|
|
60
|
+
* idempotencyKey(payload) {
|
|
61
|
+
* return payload.id
|
|
62
|
+
* }
|
|
63
|
+
* })
|
|
64
|
+
*
|
|
65
|
+
* const MyWorkflow = Workflow.make({
|
|
66
|
+
* name: "MyWorkflow",
|
|
67
|
+
* payload: {
|
|
68
|
+
* id: Schema.String
|
|
69
|
+
* },
|
|
70
|
+
* idempotencyKey: ({ id }) => id
|
|
71
|
+
* })
|
|
72
|
+
*
|
|
73
|
+
* const MyWorkflowLayer = MyWorkflow.toLayer(
|
|
74
|
+
* Effect.fn(function*() {
|
|
75
|
+
* // Add an item to the DurableQueue defined above.
|
|
76
|
+
* //
|
|
77
|
+
* // When the worker has finished processing the item, the workflow will
|
|
78
|
+
* // resume.
|
|
79
|
+
* //
|
|
80
|
+
* yield* DurableQueue.process(ApiQueue, { id: "api-call-1" })
|
|
81
|
+
*
|
|
82
|
+
* yield* Effect.log("Workflow succeeded!")
|
|
83
|
+
* })
|
|
84
|
+
* )
|
|
85
|
+
*
|
|
86
|
+
* // Define a worker layer that can process items from the DurableQueue.
|
|
87
|
+
* const ApiWorker = DurableQueue.worker(
|
|
88
|
+
* ApiQueue,
|
|
89
|
+
* Effect.fn(function*({ id }) {
|
|
90
|
+
* yield* Effect.log(`Worker processing API call with id: ${id}`)
|
|
91
|
+
* }),
|
|
92
|
+
* { concurrency: 5 } // Process up to 5 items concurrently
|
|
93
|
+
* )
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* @since 1.0.0
|
|
97
|
+
* @category Constructors
|
|
98
|
+
*/
|
|
99
|
+
export const make = <
|
|
100
|
+
Payload extends Schema.Schema.Any | Schema.Struct.Fields,
|
|
101
|
+
Success extends Schema.Schema.Any = typeof Schema.Void,
|
|
102
|
+
Error extends Schema.Schema.All = typeof Schema.Never
|
|
103
|
+
>(
|
|
104
|
+
options: {
|
|
105
|
+
readonly name: string
|
|
106
|
+
readonly payload: Payload
|
|
107
|
+
readonly idempotencyKey: (
|
|
108
|
+
payload: Payload extends Schema.Struct.Fields ? Schema.Struct<Payload>["Type"] : Payload["Type"]
|
|
109
|
+
) => string
|
|
110
|
+
readonly success?: Success | undefined
|
|
111
|
+
readonly error?: Error | undefined
|
|
112
|
+
}
|
|
113
|
+
): DurableQueue<
|
|
114
|
+
Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload,
|
|
115
|
+
Success,
|
|
116
|
+
Error
|
|
117
|
+
> => ({
|
|
118
|
+
[TypeId]: TypeId,
|
|
119
|
+
name: options.name,
|
|
120
|
+
payloadSchema: Schema.isSchema(options.payload) ? options.payload : Schema.Struct(options.payload) as any,
|
|
121
|
+
idempotencyKey: options.idempotencyKey as any,
|
|
122
|
+
deferred: DurableDeferred.make(`DurableQueue/${options.name}`, {
|
|
123
|
+
success: options.success,
|
|
124
|
+
error: options.error
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const queueSchemas = new WeakMap<Schema.Schema.Any, Schema.Schema.Any>()
|
|
129
|
+
const getQueueSchema = <Payload extends Schema.Schema.Any>(
|
|
130
|
+
payload: Payload
|
|
131
|
+
): Schema.Struct<{
|
|
132
|
+
token: typeof Schema.String
|
|
133
|
+
payload: Payload
|
|
134
|
+
traceId: typeof Schema.String
|
|
135
|
+
spanId: typeof Schema.String
|
|
136
|
+
sampled: typeof Schema.Boolean
|
|
137
|
+
}> => {
|
|
138
|
+
let schema = queueSchemas.get(payload)
|
|
139
|
+
if (!schema) {
|
|
140
|
+
schema = Schema.Struct({
|
|
141
|
+
token: Schema.String,
|
|
142
|
+
traceId: Schema.String,
|
|
143
|
+
spanId: Schema.String,
|
|
144
|
+
sampled: Schema.Boolean,
|
|
145
|
+
payload
|
|
146
|
+
})
|
|
147
|
+
queueSchemas.set(payload, schema)
|
|
148
|
+
}
|
|
149
|
+
return schema as any
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @since 1.0.0
|
|
154
|
+
* @category Processing
|
|
155
|
+
*/
|
|
156
|
+
export const process: <
|
|
157
|
+
Payload extends Schema.Schema.Any,
|
|
158
|
+
Success extends Schema.Schema.Any,
|
|
159
|
+
Error extends Schema.Schema.All
|
|
160
|
+
>(
|
|
161
|
+
self: DurableQueue<Payload, Success, Error>,
|
|
162
|
+
payload: Payload["Type"],
|
|
163
|
+
options?: {
|
|
164
|
+
readonly retrySchedule?: Schedule.Schedule<any, PersistedQueue.PersistedQueueError> | undefined
|
|
165
|
+
}
|
|
166
|
+
) => Effect.Effect<
|
|
167
|
+
Success["Type"],
|
|
168
|
+
Error["Type"],
|
|
169
|
+
| WorkflowEngine.WorkflowEngine
|
|
170
|
+
| WorkflowEngine.WorkflowInstance
|
|
171
|
+
| PersistedQueue.PersistedQueueFactory
|
|
172
|
+
| Success["Context"]
|
|
173
|
+
| Error["Context"]
|
|
174
|
+
| Payload["Context"]
|
|
175
|
+
> = Effect.fnUntraced(function*<
|
|
176
|
+
Payload extends Schema.Schema.Any,
|
|
177
|
+
Success extends Schema.Schema.Any,
|
|
178
|
+
Error extends Schema.Schema.All
|
|
179
|
+
>(self: DurableQueue<Payload, Success, Error>, payload: Payload["Type"], options?: {
|
|
180
|
+
readonly retrySchedule?: Schedule.Schedule<any, PersistedQueue.PersistedQueueError> | undefined
|
|
181
|
+
}) {
|
|
182
|
+
const key = yield* makeHashDigest(self.idempotencyKey(payload))
|
|
183
|
+
|
|
184
|
+
const deferred = DurableDeferred.make(`${self.deferred.name}/${key}`, {
|
|
185
|
+
success: self.deferred.successSchema,
|
|
186
|
+
error: self.deferred.errorSchema
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
yield* Activity.make({
|
|
190
|
+
name: `DurableQueue/${self.name}/${key}`,
|
|
191
|
+
execute: Effect.gen(function*() {
|
|
192
|
+
const span = yield* Effect.orDie(Effect.currentSpan)
|
|
193
|
+
const queue = yield* PersistedQueue.make({
|
|
194
|
+
name: `DurableQueue/${self.name}`,
|
|
195
|
+
schema: getQueueSchema(self.payloadSchema)
|
|
196
|
+
})
|
|
197
|
+
const token = yield* DurableDeferred.token(deferred)
|
|
198
|
+
yield* queue.offer({
|
|
199
|
+
token,
|
|
200
|
+
payload,
|
|
201
|
+
traceId: span.traceId,
|
|
202
|
+
spanId: span.spanId,
|
|
203
|
+
sampled: span.sampled
|
|
204
|
+
} as any).pipe(
|
|
205
|
+
Effect.tapErrorCause(Effect.logWarning),
|
|
206
|
+
Effect.catchTag("ParseError", Effect.die),
|
|
207
|
+
Effect.retry(options?.retrySchedule ?? defaultRetrySchedule),
|
|
208
|
+
Effect.orDie,
|
|
209
|
+
Effect.annotateLogs({
|
|
210
|
+
package: "@effect/workflow",
|
|
211
|
+
module: "DurableQueue",
|
|
212
|
+
fiber: "process",
|
|
213
|
+
queueName: self.name
|
|
214
|
+
})
|
|
215
|
+
)
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return yield* DurableDeferred.await(deferred)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const defaultRetrySchedule = Schedule.exponential(500, 1.5).pipe(
|
|
223
|
+
Schedule.union(Schedule.spaced("1 minute"))
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @since 1.0.0
|
|
228
|
+
* @category Worker
|
|
229
|
+
*/
|
|
230
|
+
export const makeWorker: <
|
|
231
|
+
Payload extends Schema.Schema.Any,
|
|
232
|
+
Success extends Schema.Schema.Any,
|
|
233
|
+
Error extends Schema.Schema.All,
|
|
234
|
+
R
|
|
235
|
+
>(
|
|
236
|
+
self: DurableQueue<Payload, Success, Error>,
|
|
237
|
+
f: (payload: Payload["Type"]) => Effect.Effect<Success["Type"], Error["Type"], R>,
|
|
238
|
+
options?: { readonly concurrency?: number | undefined } | undefined
|
|
239
|
+
) => Effect.Effect<
|
|
240
|
+
never,
|
|
241
|
+
never,
|
|
242
|
+
| WorkflowEngine.WorkflowEngine
|
|
243
|
+
| PersistedQueue.PersistedQueueFactory
|
|
244
|
+
| R
|
|
245
|
+
| Payload["Context"]
|
|
246
|
+
| Success["Context"]
|
|
247
|
+
| Error["Context"]
|
|
248
|
+
> = Effect.fnUntraced(function*<
|
|
249
|
+
Payload extends Schema.Schema.Any,
|
|
250
|
+
Success extends Schema.Schema.Any,
|
|
251
|
+
Error extends Schema.Schema.All,
|
|
252
|
+
R
|
|
253
|
+
>(
|
|
254
|
+
self: DurableQueue<Payload, Success, Error>,
|
|
255
|
+
f: (payload: Payload["Type"]) => Effect.Effect<Success["Type"], Error["Type"], R>,
|
|
256
|
+
options?: {
|
|
257
|
+
readonly concurrency?: number | undefined
|
|
258
|
+
}
|
|
259
|
+
) {
|
|
260
|
+
const queue = yield* PersistedQueue.make({
|
|
261
|
+
name: `DurableQueue/${self.name}`,
|
|
262
|
+
schema: getQueueSchema(self.payloadSchema)
|
|
263
|
+
})
|
|
264
|
+
const concurrency = options?.concurrency ?? 1
|
|
265
|
+
|
|
266
|
+
const worker = queue.take((item_) => {
|
|
267
|
+
const item = item_ as any as {
|
|
268
|
+
token: DurableDeferred.Token
|
|
269
|
+
payload: Payload["Type"]
|
|
270
|
+
traceId: string
|
|
271
|
+
spanId: string
|
|
272
|
+
sampled: boolean
|
|
273
|
+
}
|
|
274
|
+
return f(item.payload).pipe(
|
|
275
|
+
Effect.exit,
|
|
276
|
+
Effect.flatMap((exit) =>
|
|
277
|
+
DurableDeferred.done(self.deferred, {
|
|
278
|
+
token: item.token,
|
|
279
|
+
exit
|
|
280
|
+
})
|
|
281
|
+
),
|
|
282
|
+
Effect.asVoid,
|
|
283
|
+
Effect.withSpan(`DurableQueue/${self.name}/worker`, {
|
|
284
|
+
captureStackTrace: false,
|
|
285
|
+
parent: {
|
|
286
|
+
_tag: "ExternalSpan",
|
|
287
|
+
traceId: item.traceId,
|
|
288
|
+
spanId: item.spanId,
|
|
289
|
+
sampled: item.sampled,
|
|
290
|
+
context: Context.empty()
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
)
|
|
294
|
+
}).pipe(
|
|
295
|
+
Effect.catchAllCause(Effect.logWarning),
|
|
296
|
+
Effect.forever,
|
|
297
|
+
Effect.annotateLogs({
|
|
298
|
+
package: "@effect/workflow",
|
|
299
|
+
module: "DurableQueue",
|
|
300
|
+
fiber: "worker"
|
|
301
|
+
})
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
yield* Effect.replicateEffect(worker, concurrency, { concurrency, discard: true })
|
|
305
|
+
return yield* Effect.never
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* @since 1.0.0
|
|
310
|
+
* @category Worker
|
|
311
|
+
*/
|
|
312
|
+
export const worker: <
|
|
313
|
+
Payload extends Schema.Schema.Any,
|
|
314
|
+
Success extends Schema.Schema.Any,
|
|
315
|
+
Error extends Schema.Schema.All,
|
|
316
|
+
R
|
|
317
|
+
>(
|
|
318
|
+
self: DurableQueue<Payload, Success, Error>,
|
|
319
|
+
f: (payload: Payload["Type"]) => Effect.Effect<Success["Type"], Error["Type"], R>,
|
|
320
|
+
options?: {
|
|
321
|
+
readonly concurrency?: number | undefined
|
|
322
|
+
} | undefined
|
|
323
|
+
) => Layer.Layer<
|
|
324
|
+
never,
|
|
325
|
+
never,
|
|
326
|
+
| WorkflowEngine.WorkflowEngine
|
|
327
|
+
| PersistedQueue.PersistedQueueFactory
|
|
328
|
+
| R
|
|
329
|
+
| Payload["Context"]
|
|
330
|
+
| Success["Context"]
|
|
331
|
+
| Error["Context"]
|
|
332
|
+
> = (self, f, options) => Layer.scopedDiscard(Effect.forkScoped(makeWorker(self, f, options)))
|
package/src/Workflow.ts
CHANGED
|
@@ -13,7 +13,7 @@ import * as Option from "effect/Option"
|
|
|
13
13
|
import type { Pipeable } from "effect/Pipeable"
|
|
14
14
|
import * as Predicate from "effect/Predicate"
|
|
15
15
|
import * as PrimaryKey from "effect/PrimaryKey"
|
|
16
|
-
import * as Schedule from "effect/Schedule"
|
|
16
|
+
import type * as Schedule from "effect/Schedule"
|
|
17
17
|
import * as Schema from "effect/Schema"
|
|
18
18
|
import type * as AST from "effect/SchemaAST"
|
|
19
19
|
import type * as Scope from "effect/Scope"
|
|
@@ -151,7 +151,7 @@ export interface Workflow<
|
|
|
151
151
|
executionId: string
|
|
152
152
|
) => Effect.Effect<Success["Type"], Error["Type"], R>
|
|
153
153
|
) => Layer.Layer<
|
|
154
|
-
|
|
154
|
+
never,
|
|
155
155
|
never,
|
|
156
156
|
| WorkflowEngine
|
|
157
157
|
| Exclude<R, WorkflowEngine | WorkflowInstance | Execution<Name> | Scope.Scope>
|
|
@@ -278,7 +278,6 @@ export const make = <
|
|
|
278
278
|
readonly annotations?: Context.Context<never>
|
|
279
279
|
}
|
|
280
280
|
): Workflow<Name, Payload extends Schema.Struct.Fields ? Schema.Struct<Payload> : Payload, Success, Error> => {
|
|
281
|
-
const suspendedRetrySchedule = options.suspendedRetrySchedule ?? defaultRetrySchedule
|
|
282
281
|
const makeExecutionId = (payload: any) => makeHashDigest(`${options.name}-${options.idempotencyKey(payload)}`)
|
|
283
282
|
const self: Workflow<Name, any, Success, Error> = {
|
|
284
283
|
[TypeId]: TypeId,
|
|
@@ -305,61 +304,19 @@ export const make = <
|
|
|
305
304
|
const engine = yield* EngineTag
|
|
306
305
|
const executionId = yield* makeExecutionId(payload)
|
|
307
306
|
yield* Effect.annotateCurrentSpan({ executionId })
|
|
308
|
-
|
|
309
|
-
const parentInstance = yield* Effect.serviceOption(InstanceTag)
|
|
310
|
-
let result: Result<unknown, unknown>
|
|
311
|
-
if (Option.isSome(parentInstance)) {
|
|
312
|
-
const instance = parentInstance.value
|
|
313
|
-
yield* Effect.addFinalizer(() => {
|
|
314
|
-
if (!instance.interrupted || result?._tag === "Complete") {
|
|
315
|
-
return Effect.void
|
|
316
|
-
}
|
|
317
|
-
return engine.interrupt(self, executionId)
|
|
318
|
-
})
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (opts?.discard) {
|
|
322
|
-
yield* engine.execute({
|
|
323
|
-
workflow: self,
|
|
324
|
-
executionId,
|
|
325
|
-
payload,
|
|
326
|
-
discard: true
|
|
327
|
-
})
|
|
328
|
-
return executionId
|
|
329
|
-
}
|
|
330
|
-
const run = engine.execute({
|
|
331
|
-
workflow: self,
|
|
307
|
+
return yield* engine.execute(self, {
|
|
332
308
|
executionId,
|
|
333
309
|
payload,
|
|
334
|
-
discard:
|
|
335
|
-
|
|
310
|
+
discard: opts?.discard,
|
|
311
|
+
suspendedRetrySchedule: options.suspendedRetrySchedule
|
|
336
312
|
})
|
|
337
|
-
if (Option.isSome(parentInstance)) {
|
|
338
|
-
result = yield* run
|
|
339
|
-
if (result._tag === "Suspended") {
|
|
340
|
-
return yield* suspend(parentInstance.value)
|
|
341
|
-
}
|
|
342
|
-
return yield* result.exit as Exit.Exit<Success["Type"], Error["Type"]>
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
let sleep: Effect.Effect<any> | undefined
|
|
346
|
-
while (true) {
|
|
347
|
-
result = yield* run
|
|
348
|
-
if (result._tag === "Complete") {
|
|
349
|
-
return yield* result.exit as Exit.Exit<Success["Type"], Error["Type"]>
|
|
350
|
-
}
|
|
351
|
-
sleep ??= (yield* Schedule.driver(suspendedRetrySchedule)).next(void 0).pipe(
|
|
352
|
-
Effect.catchAll(() => Effect.dieMessage(`${options.name}.execute: suspendedRetrySchedule exhausted`))
|
|
353
|
-
)
|
|
354
|
-
yield* sleep
|
|
355
|
-
}
|
|
356
313
|
},
|
|
357
314
|
Effect.withSpan(`${options.name}.execute`, { captureStackTrace: false })
|
|
358
315
|
),
|
|
359
316
|
poll: Effect.fnUntraced(
|
|
360
317
|
function*(executionId: string) {
|
|
361
318
|
const engine = yield* EngineTag
|
|
362
|
-
return yield* engine.poll(
|
|
319
|
+
return yield* engine.poll(self, executionId)
|
|
363
320
|
},
|
|
364
321
|
(effect, executionId) =>
|
|
365
322
|
Effect.withSpan(effect, `${options.name}.poll`, {
|
|
@@ -390,14 +347,9 @@ export const make = <
|
|
|
390
347
|
})
|
|
391
348
|
),
|
|
392
349
|
toLayer: (execute) =>
|
|
393
|
-
Layer.
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
yield* engine.register(self, (payload, executionId) =>
|
|
397
|
-
Effect.suspend(() => execute(payload, executionId)).pipe(
|
|
398
|
-
Effect.mapInputContext((input) => Context.merge(context, input))
|
|
399
|
-
) as any)
|
|
400
|
-
return EngineTag.context(engine)
|
|
350
|
+
Layer.scopedDiscard(Effect.gen(function*() {
|
|
351
|
+
const engine = yield* EngineTag
|
|
352
|
+
return yield* engine.register(self, execute)
|
|
401
353
|
})) as any,
|
|
402
354
|
executionId: (payload) => makeExecutionId(self.payloadSchema.make(payload)),
|
|
403
355
|
withCompensation
|
|
@@ -406,10 +358,6 @@ export const make = <
|
|
|
406
358
|
return self
|
|
407
359
|
}
|
|
408
360
|
|
|
409
|
-
const defaultRetrySchedule = Schedule.exponential(200, 1.5).pipe(
|
|
410
|
-
Schedule.union(Schedule.spaced(30000))
|
|
411
|
-
)
|
|
412
|
-
|
|
413
361
|
/**
|
|
414
362
|
* @since 1.0.0
|
|
415
363
|
* @category Constructors
|