@solidactions/sdk 0.1.0 → 0.2.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.
- package/README.md +28 -4
- package/dist/src/adminserver.d.ts +0 -2
- package/dist/src/adminserver.d.ts.map +1 -1
- package/dist/src/adminserver.js +17 -7
- package/dist/src/adminserver.js.map +1 -1
- package/dist/src/authdecorators.js +1 -2
- package/dist/src/authdecorators.js.map +1 -1
- package/dist/src/cli/cli.js +0 -0
- package/dist/src/conductor/conductor.d.ts +0 -1
- package/dist/src/conductor/conductor.d.ts.map +1 -1
- package/dist/src/conductor/conductor.js +17 -7
- package/dist/src/conductor/conductor.js.map +1 -1
- package/dist/src/config.js +11 -11
- package/dist/src/config.js.map +1 -1
- package/dist/src/context.d.ts +0 -2
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.js +8 -9
- package/dist/src/context.js.map +1 -1
- package/dist/src/database_utils.js +3 -4
- package/dist/src/database_utils.js.map +1 -1
- package/dist/src/debugpoint.js +5 -5
- package/dist/src/debugpoint.js.map +1 -1
- package/dist/src/decorators.d.ts.map +1 -1
- package/dist/src/decorators.js +35 -35
- package/dist/src/decorators.js.map +1 -1
- package/dist/src/error.d.ts +2 -2
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js +3 -3
- package/dist/src/error.js.map +1 -1
- package/dist/src/http_client.d.ts +7 -1
- package/dist/src/http_client.d.ts.map +1 -1
- package/dist/src/http_client.js +17 -6
- package/dist/src/http_client.js.map +1 -1
- package/dist/src/http_system_database.d.ts +1 -0
- package/dist/src/http_system_database.d.ts.map +1 -1
- package/dist/src/http_system_database.js +11 -1
- package/dist/src/http_system_database.js.map +1 -1
- package/dist/src/index.js +17 -7
- package/dist/src/index.js.map +1 -1
- package/dist/src/serialization.d.ts.map +1 -1
- package/dist/src/serialization.js +7 -7
- package/dist/src/serialization.js.map +1 -1
- package/dist/src/solidactions-executor.js +17 -7
- package/dist/src/solidactions-executor.js.map +1 -1
- package/dist/src/solidactions.d.ts +23 -3
- package/dist/src/solidactions.d.ts.map +1 -1
- package/dist/src/solidactions.js +30 -4
- package/dist/src/solidactions.js.map +1 -1
- package/dist/src/system_database.d.ts +1 -0
- package/dist/src/system_database.d.ts.map +1 -1
- package/dist/src/system_database.js.map +1 -1
- package/dist/src/telemetry/collector.d.ts.map +1 -1
- package/dist/src/telemetry/logs.d.ts.map +1 -1
- package/dist/src/telemetry/traces.js +5 -5
- package/dist/src/telemetry/traces.js.map +1 -1
- package/dist/src/testing/index.d.ts +13 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +19 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/mock_server.d.ts +134 -0
- package/dist/src/testing/mock_server.d.ts.map +1 -0
- package/dist/src/testing/mock_server.js +613 -0
- package/dist/src/testing/mock_server.js.map +1 -0
- package/dist/src/utils.d.ts +0 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +8 -8
- package/dist/src/utils.js.map +1 -1
- package/dist/src/workflow.d.ts +1 -1
- package/dist/src/workflow.d.ts.map +1 -1
- package/dist/src/workflow.js +2 -2
- package/dist/src/workflow.js.map +1 -1
- package/dist/src/workflow_management.js +7 -8
- package/dist/src/workflow_management.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/sdk-reference.md +1207 -0
- package/package.json +6 -1
- package/.claude/settings.local.json +0 -7
- package/dist/dbos-config.schema.json +0 -132
- package/dist/src/cli/commands.d.ts +0 -3
- package/dist/src/cli/commands.d.ts.map +0 -1
- package/dist/src/cli/commands.js +0 -46
- package/dist/src/cli/commands.js.map +0 -1
- package/dist/src/datasource.d.ts +0 -109
- package/dist/src/datasource.d.ts.map +0 -1
- package/dist/src/datasource.js +0 -204
- package/dist/src/datasource.js.map +0 -1
- package/dist/src/dbos-executor.d.ts +0 -189
- package/dist/src/dbos-executor.d.ts.map +0 -1
- package/dist/src/dbos-executor.js +0 -817
- package/dist/src/dbos-executor.js.map +0 -1
- package/dist/src/dbos.d.ts +0 -519
- package/dist/src/dbos.d.ts.map +0 -1
- package/dist/src/dbos.js +0 -1282
- package/dist/src/dbos.js.map +0 -1
- package/dist/src/debouncer.d.ts +0 -33
- package/dist/src/debouncer.d.ts.map +0 -1
- package/dist/src/debouncer.js +0 -170
- package/dist/src/debouncer.js.map +0 -1
- package/dist/src/scheduler/crontab.d.ts +0 -14
- package/dist/src/scheduler/crontab.d.ts.map +0 -1
- package/dist/src/scheduler/crontab.js +0 -308
- package/dist/src/scheduler/crontab.js.map +0 -1
- package/dist/src/scheduler/scheduler.d.ts +0 -41
- package/dist/src/scheduler/scheduler.d.ts.map +0 -1
- package/dist/src/scheduler/scheduler.js +0 -165
- package/dist/src/scheduler/scheduler.js.map +0 -1
- package/dist/src/wfqueue.d.ts +0 -64
- package/dist/src/wfqueue.d.ts.map +0 -1
- package/dist/src/wfqueue.js +0 -147
- package/dist/src/wfqueue.js.map +0 -1
- package/docs/api-schema.md +0 -1441
- package/docs/migration-guide.md +0 -460
- package/docs/phase-14-changes.md +0 -156
- package/docs/solidsteps-ai-prompt.md +0 -534
- package/solidactions-ai-prompt.md +0 -1504
|
@@ -1,1504 +0,0 @@
|
|
|
1
|
-
# Build Reliable Applications With SolidActions
|
|
2
|
-
|
|
3
|
-
## Guidelines
|
|
4
|
-
|
|
5
|
-
- Respond in a friendly and concise manner
|
|
6
|
-
- Ask clarifying questions when requirements are ambiguous
|
|
7
|
-
- Generate code in TypeScript using the SolidActions library. Make sure to fully type everything.
|
|
8
|
-
- You MUST import all methods and classes used in the code you generate
|
|
9
|
-
- You SHALL keep all code in a single file unless otherwise specified.
|
|
10
|
-
- You MUST await all promises.
|
|
11
|
-
- SolidActions is a workflow automation SDK.
|
|
12
|
-
|
|
13
|
-
## Workflow Guidelines
|
|
14
|
-
|
|
15
|
-
Workflows provide durable execution so you can write programs that are resilient to any failure.
|
|
16
|
-
Workflows are comprised of steps, which are ordinary TypeScript functions called with SolidActions.runStep().
|
|
17
|
-
When using SolidActions workflows, you should call any function that performs complex operations or accesses external APIs or services as a step using SolidActions.runStep.
|
|
18
|
-
|
|
19
|
-
If a workflow is interrupted for any reason (e.g., an executor restarts or crashes), when your program restarts the workflow automatically resumes execution from the last completed step.
|
|
20
|
-
|
|
21
|
-
- If asked to add SolidActions to existing code, you MUST ask which function to make a workflow. Do NOT recommend any changes until they have told you what function to make a workflow. Do NOT make a function a workflow unless SPECIFICALLY requested.
|
|
22
|
-
- When making a function a workflow, you should make all functions it calls steps. Do NOT change the functions in any way.
|
|
23
|
-
- Do NOT make functions steps unless they are DIRECTLY called by a workflow.
|
|
24
|
-
- If the workflow function performs a non-deterministic action, you MUST move that action to its own function and make that function a step. Examples of non-deterministic actions include accessing an external API or service, accessing files on disk, generating a random number, of getting the current time.
|
|
25
|
-
- Do NOT use Promise.all() due to the risks posed by multiple rejections. Using Promise.allSettled() for parallelism is allowed for single-step promises only. For any complex parallel execution, you should instead use SolidActions.startWorkflow and SolidActions queues to achieve the parallelism.
|
|
26
|
-
- SolidActions workflows and steps should NOT have side effects in memory outside of their own scope. They can access global variables, but they should NOT create or update global variables or variables outside their scope.
|
|
27
|
-
- Do NOT call any SolidActions context method (SolidActions.send, SolidActions.recv, SolidActions.startWorkflow, SolidActions.sleep, SolidActions.setEvent, SolidActions.getEvent) from a step.
|
|
28
|
-
- Do NOT start workflows from inside a step.
|
|
29
|
-
- Do NOT call SolidActions.setEvent and SolidActions.recv from outside a workflow function.
|
|
30
|
-
- Do NOT use SolidActions.getApi, SolidActions.postApi, or other SolidActions HTTP annotations. These are DEPRECATED. Instead, use Express for HTTP serving by default, unless another web framework is specified.
|
|
31
|
-
|
|
32
|
-
## SolidActions Lifecycle Guidelines
|
|
33
|
-
|
|
34
|
-
SolidActions should be installed and imported from the `@dbos-inc/dbos-sdk` package.
|
|
35
|
-
Due to its internal workflow registry, The SolidActions library and SolidActions workflows cannot be bundled with JavaScript or TypeScript bundlers (Webpack, Vite, Rollup, esbuild, Parcel, etc.) and must be treated as an external library by these tools. Configuration for bundlers should be suggested if these tools are in use and cannot be avoided.
|
|
36
|
-
|
|
37
|
-
SolidActions does not support "serverless" frameworks due to its long-running background jobs. SolidActions programs MUST have a starting file (typically 'main.ts' or 'server.ts') that creates all objects and workflow functions during startup.
|
|
38
|
-
|
|
39
|
-
Any SolidActions program MUST call SolidActions.setConfig and SolidActions.launch in its main function, like so.
|
|
40
|
-
You MUST use this default configuration (changing the name as appropriate) unless otherwise specified.
|
|
41
|
-
|
|
42
|
-
```javascript
|
|
43
|
-
SolidActions.setConfig({
|
|
44
|
-
name: 'dbos-node-starter',
|
|
45
|
-
systemDatabaseUrl: process.env.SolidActions_SYSTEM_DATABASE_URL,
|
|
46
|
-
});
|
|
47
|
-
await SolidActions.launch();
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Here is an example main function using Express:
|
|
51
|
-
|
|
52
|
-
```javascript
|
|
53
|
-
import { SolidActions } from '@dbos-inc/dbos-sdk';
|
|
54
|
-
|
|
55
|
-
async function main() {
|
|
56
|
-
SolidActions.setConfig({
|
|
57
|
-
name: 'dbos-node-starter',
|
|
58
|
-
systemDatabaseUrl: process.env.SolidActions_SYSTEM_DATABASE_URL,
|
|
59
|
-
});
|
|
60
|
-
await SolidActions.launch();
|
|
61
|
-
const PORT = 3000;
|
|
62
|
-
app.listen(PORT, () => {
|
|
63
|
-
console.log(`🚀 Server is running on http://localhost:${PORT}`);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
main().catch(console.log);
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## Workflow and Steps Examples
|
|
71
|
-
|
|
72
|
-
Simple example:
|
|
73
|
-
|
|
74
|
-
```javascript
|
|
75
|
-
import { SolidActions } from '@dbos-inc/dbos-sdk';
|
|
76
|
-
|
|
77
|
-
async function stepOne() {
|
|
78
|
-
SolidActions.logger.info('Step one completed!');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function stepTwo() {
|
|
82
|
-
SolidActions.logger.info('Step two completed!');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function exampleFunction() {
|
|
86
|
-
await SolidActions.runStep(() => stepOne());
|
|
87
|
-
await SolidActions.runStep(() => stepTwo());
|
|
88
|
-
}
|
|
89
|
-
const exampleWorkflow = SolidActions.registerWorkflow(exampleFunction);
|
|
90
|
-
|
|
91
|
-
async function main() {
|
|
92
|
-
SolidActions.setConfig({
|
|
93
|
-
name: 'dbos-node-starter',
|
|
94
|
-
systemDatabaseUrl: process.env.SolidActions_SYSTEM_DATABASE_URL,
|
|
95
|
-
});
|
|
96
|
-
await SolidActions.launch();
|
|
97
|
-
await exampleWorkflow();
|
|
98
|
-
await SolidActions.shutdown();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
main().catch(console.log);
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
Example with Express:
|
|
105
|
-
|
|
106
|
-
```javascript
|
|
107
|
-
import { SolidActions } from '@dbos-inc/dbos-sdk';
|
|
108
|
-
import express from 'express';
|
|
109
|
-
|
|
110
|
-
export const app = express();
|
|
111
|
-
app.use(express.json());
|
|
112
|
-
|
|
113
|
-
async function stepOne() {
|
|
114
|
-
SolidActions.logger.info('Step one completed!');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function stepTwo() {
|
|
118
|
-
SolidActions.logger.info('Step two completed!');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async function exampleFunction() {
|
|
122
|
-
await SolidActions.runStep(() => stepOne());
|
|
123
|
-
await SolidActions.runStep(() => stepTwo());
|
|
124
|
-
}
|
|
125
|
-
const exampleWorkflow = SolidActions.registerWorkflow(exampleFunction);
|
|
126
|
-
|
|
127
|
-
app.get('/', async (req, res) => {
|
|
128
|
-
await exampleWorkflow();
|
|
129
|
-
res.send();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
async function main() {
|
|
133
|
-
SolidActions.setConfig({
|
|
134
|
-
name: 'dbos-node-starter',
|
|
135
|
-
systemDatabaseUrl: process.env.SolidActions_SYSTEM_DATABASE_URL,
|
|
136
|
-
});
|
|
137
|
-
await SolidActions.launch();
|
|
138
|
-
const PORT = 3000;
|
|
139
|
-
app.listen(PORT, () => {
|
|
140
|
-
console.log(`🚀 Server is running on http://localhost:${PORT}`);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
main().catch(console.log);
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
Example with queues:
|
|
148
|
-
|
|
149
|
-
```javascript
|
|
150
|
-
import { SolidActions, WorkflowQueue } from "@dbos-inc/dbos-sdk";
|
|
151
|
-
import express from "express";
|
|
152
|
-
|
|
153
|
-
export const app = express();
|
|
154
|
-
app.use(express.json());
|
|
155
|
-
|
|
156
|
-
const queue = new WorkflowQueue("example_queue");
|
|
157
|
-
|
|
158
|
-
async function taskFunction(n: number) {
|
|
159
|
-
await SolidActions.sleep(5000);
|
|
160
|
-
SolidActions.logger.info(`Task ${n} completed!`)
|
|
161
|
-
}
|
|
162
|
-
const taskWorkflow = SolidActions.registerWorkflow(taskFunction);
|
|
163
|
-
|
|
164
|
-
async function queueFunction() {
|
|
165
|
-
SolidActions.logger.info("Enqueueing tasks!")
|
|
166
|
-
const handles = []
|
|
167
|
-
for (let i = 0; i < 10; i++) {
|
|
168
|
-
handles.push(await SolidActions.startWorkflow(taskWorkflow, { queueName: queue.name })(i))
|
|
169
|
-
}
|
|
170
|
-
const results = []
|
|
171
|
-
for (const h of handles) {
|
|
172
|
-
results.push(await h.getResult())
|
|
173
|
-
}
|
|
174
|
-
SolidActions.logger.info(`Successfully completed ${results.length} tasks`)
|
|
175
|
-
}
|
|
176
|
-
const queueWorkflow = SolidActions.registerWorkflow(queueFunction)
|
|
177
|
-
|
|
178
|
-
app.get("/", async (req, res) => {
|
|
179
|
-
await queueWorkflow();
|
|
180
|
-
res.send();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
async function main() {
|
|
184
|
-
SolidActions.setConfig({
|
|
185
|
-
"name": "dbos-node-starter",
|
|
186
|
-
"systemDatabaseUrl": process.env.SolidActions_SYSTEM_DATABASE_URL,
|
|
187
|
-
});
|
|
188
|
-
await SolidActions.launch();
|
|
189
|
-
const PORT = 3000;
|
|
190
|
-
app.listen(PORT, () => {
|
|
191
|
-
console.log(`🚀 Server is running on http://localhost:${PORT}`);
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
main().catch(console.log);
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
### Scheduled Workflow
|
|
199
|
-
|
|
200
|
-
You can schedule SolidActions workflows to run exactly once per time interval.
|
|
201
|
-
To do this, use the the `SolidActions.registerScheduled` method or the `SolidActions.scheduled` decorator and specify the schedule in crontab syntax. For example:
|
|
202
|
-
|
|
203
|
-
- A scheduled workflow MUST specify a crontab schedule.
|
|
204
|
-
- It MUST take in two arguments, scheduled and actual time. Both are Date of when the workflow started.
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
async function scheduledFunction(schedTime: Date, startTime: Date) {
|
|
208
|
-
SolidActions.logger.info(`I am a workflow scheduled to run every 30 seconds`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const scheduledWorkflow = SolidActions.registerWorkflow(scheduledFunction);
|
|
212
|
-
SolidActions.registerScheduled(scheduledWorkflow, { crontab: '*/30 * * * * *' });
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Or using decorators:
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
class ScheduledExample {
|
|
219
|
-
@SolidActions.workflow()
|
|
220
|
-
@SolidActions.scheduled({ crontab: '*/30 * * * * *' })
|
|
221
|
-
static async scheduledWorkflow(schedTime: Date, startTime: Date) {
|
|
222
|
-
SolidActions.logger.info(`I am a workflow scheduled to run every 30 seconds`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
## Workflow Documentation:
|
|
228
|
-
|
|
229
|
-
Workflows provide **durable execution** so you can write programs that are **resilient to any failure**.
|
|
230
|
-
Workflows are comprised of steps, which wrap ordinary TypeScript (or JavaScript) functions.
|
|
231
|
-
If a workflow is interrupted for any reason (e.g., an executor restarts or crashes), when your program restarts the workflow automatically resumes execution from the last completed step.
|
|
232
|
-
|
|
233
|
-
To write a workflow, register a TypeScript function with `SolidActions.registerWorkflow`.
|
|
234
|
-
The function's inputs and outputs must be serializable to JSON.
|
|
235
|
-
For example:
|
|
236
|
-
|
|
237
|
-
```typescript
|
|
238
|
-
async function stepOne() {
|
|
239
|
-
SolidActions.logger.info('Step one completed!');
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function stepTwo() {
|
|
243
|
-
SolidActions.logger.info('Step two completed!');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function workflowFunction() {
|
|
247
|
-
await SolidActions.runStep(() => stepOne(), { name: 'stepOne' });
|
|
248
|
-
await SolidActions.runStep(() => stepTwo(), { name: 'stepTwo' });
|
|
249
|
-
}
|
|
250
|
-
const workflow = SolidActions.registerWorkflow(workflowFunction);
|
|
251
|
-
|
|
252
|
-
await workflow();
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
Alternatively, you can register workflows and steps with decorators:
|
|
256
|
-
|
|
257
|
-
```typescript
|
|
258
|
-
export class Example {
|
|
259
|
-
@SolidActions.step()
|
|
260
|
-
static async stepOne() {
|
|
261
|
-
SolidActions.logger.info('Step one completed!');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
@SolidActions.step()
|
|
265
|
-
static async stepTwo() {
|
|
266
|
-
SolidActions.logger.info('Step two completed!');
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Call steps from workflows
|
|
270
|
-
@SolidActions.workflow()
|
|
271
|
-
static async exampleWorkflow() {
|
|
272
|
-
await Toolbox.stepOne();
|
|
273
|
-
await Toolbox.stepTwo();
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
await Example.exampleWorkflow();
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
## Starting Workflows In The Background
|
|
281
|
-
|
|
282
|
-
One common use-case for workflows is building reliable background tasks that keep running even when your program is interrupted, restarted, or crashes.
|
|
283
|
-
You can use `SolidActions.startWorkflow` to start a workflow in the background.
|
|
284
|
-
If you start a workflow this way, it returns a workflow handle, from which you can access information about the workflow or wait for it to complete and retrieve its result.
|
|
285
|
-
|
|
286
|
-
Here's an example:
|
|
287
|
-
|
|
288
|
-
```javascript
|
|
289
|
-
class Example {
|
|
290
|
-
@SolidActions.workflow()
|
|
291
|
-
static async exampleWorkflow(var1: string, var2: string) {
|
|
292
|
-
return var1 + var2;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async function main() {
|
|
297
|
-
// Start exampleWorkflow in the background
|
|
298
|
-
const handle = await SolidActions.startWorkflow(Example).exampleWorkflow("one", "two");
|
|
299
|
-
// Wait for the workflow to complete and return its results
|
|
300
|
-
const result = await handle.getResult();
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
After starting a workflow in the background, you can use `SolidActions.retrieveWorkflow` to retrieve a workflow's handle from its ID.
|
|
305
|
-
You can also retrieve a workflow's handle from outside of your SolidActions application with 'SolidActionsClient.retrieveWorkflow`.
|
|
306
|
-
|
|
307
|
-
If you need to run many workflows in the background and manage their concurrency or flow control, you can also use SolidActions queues.
|
|
308
|
-
|
|
309
|
-
## Workflow IDs and Idempotency
|
|
310
|
-
|
|
311
|
-
Every time you execute a workflow, that execution is assigned a unique ID, by default a UUID.
|
|
312
|
-
You can access this ID through the `SolidActions.workflowID` context variable.
|
|
313
|
-
Workflow IDs are useful for communicating with workflows and developing interactive workflows.
|
|
314
|
-
|
|
315
|
-
You can set the workflow ID of a workflow as an argument to `SolidActions.startWorkflow()`.
|
|
316
|
-
Workflow IDs must be **globally unique** for your application.
|
|
317
|
-
An assigned workflow ID acts as an idempotency key: if a workflow is called multiple times with the same ID, it executes only once.
|
|
318
|
-
This is useful if your operations have side effects like making a payment or sending an email.
|
|
319
|
-
Workflow IDs are also useful for communicating with workflows and developing interactive workflows - see Communicating with Workflows for more details.
|
|
320
|
-
|
|
321
|
-
For example:
|
|
322
|
-
|
|
323
|
-
```javascript
|
|
324
|
-
class Example {
|
|
325
|
-
@SolidActions.workflow()
|
|
326
|
-
static async exampleWorkflow(var1: string, var2: string) {
|
|
327
|
-
// ...
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async function main() {
|
|
332
|
-
const myID: string = ...
|
|
333
|
-
const handle = await SolidActions.startWorkflow(Example, {workflowID: myID}).exampleWorkflow("one", "two");
|
|
334
|
-
const result = await handle.getResult();
|
|
335
|
-
}
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
## Determinism
|
|
339
|
-
|
|
340
|
-
Workflows are in most respects normal TypeScript functions.
|
|
341
|
-
They can have loops, branches, conditionals, and so on.
|
|
342
|
-
However, a workflow function must be **deterministic**: if called multiple times with the same inputs, it should invoke the same steps with the same inputs in the same order (given the same return values from those steps).
|
|
343
|
-
If you need to perform a non-deterministic operation like accessing the database, calling a third-party API, generating a random number, or getting the local time, you shouldn't do it directly in a workflow function.
|
|
344
|
-
Instead, you should do all database operations in transactions and all other non-deterministic operations in steps.
|
|
345
|
-
|
|
346
|
-
For example, **don't do this**:
|
|
347
|
-
|
|
348
|
-
```javascript
|
|
349
|
-
class Example {
|
|
350
|
-
@SolidActions.workflow()
|
|
351
|
-
static async exampleWorkflow() {
|
|
352
|
-
// Don't make an HTTP request in a workflow function
|
|
353
|
-
const body = await fetch('https://example.com').then((r) => r.text());
|
|
354
|
-
await Example.exampleTransaction(body);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
Instead, do this:
|
|
360
|
-
|
|
361
|
-
```javascript
|
|
362
|
-
class Example {
|
|
363
|
-
@SolidActions.workflow()
|
|
364
|
-
static async exampleWorkflow() {
|
|
365
|
-
// Don't make an HTTP request in a workflow function
|
|
366
|
-
const body = await SolidActions.runStep(
|
|
367
|
-
async () => {
|
|
368
|
-
return await fetch('https://example.com').then((r) => r.text());
|
|
369
|
-
},
|
|
370
|
-
{ name: 'fetchBody' },
|
|
371
|
-
);
|
|
372
|
-
await Example.exampleTransaction(body);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
Or this:
|
|
378
|
-
|
|
379
|
-
```javascript
|
|
380
|
-
class Example {
|
|
381
|
-
@SolidActions.step()
|
|
382
|
-
static async fetchBody() {
|
|
383
|
-
// Instead, make HTTP requests in steps
|
|
384
|
-
return await fetch('https://example.com').then((r) => r.text());
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
@SolidActions.workflow()
|
|
388
|
-
static async exampleWorkflow() {
|
|
389
|
-
const body = await Example.fetchBody();
|
|
390
|
-
await Example.exampleTransaction(body);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### Running Steps In Parallel
|
|
396
|
-
|
|
397
|
-
Initiating several concurrent steps in a workflow, followed by awaiting them with `Promise.allSettled`, is valid as long as the steps are started in a deterministic order. For example the following is allowed:
|
|
398
|
-
|
|
399
|
-
```typescript
|
|
400
|
-
const results = await Promise.allSettled([step1('arg1'), step2('arg2'), step3('arg3'), step4('arg4')]);
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
This is allowed because each step is started in a well-defined sequence before awaiting.
|
|
404
|
-
|
|
405
|
-
By contrast, the following is not allowed:
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
const results = await Promise.allSettled([
|
|
409
|
-
async () => {
|
|
410
|
-
await step1('arg1');
|
|
411
|
-
await step2('arg3');
|
|
412
|
-
},
|
|
413
|
-
async () => {
|
|
414
|
-
await step3('arg2');
|
|
415
|
-
await step4('arg4');
|
|
416
|
-
},
|
|
417
|
-
]);
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
Here, `step2` and `step4` may be started in either order since their execution depends on the relative time taken by `step1` and `step3`.
|
|
421
|
-
|
|
422
|
-
If you need to run sequences of operations concurrently, start child workflows with `startWorkflow` and await the results from their `WorkflowHandle`s.
|
|
423
|
-
|
|
424
|
-
Avoid using `Promise.all` because of how it handles errors and rejections. When any promise rejects, `Promise.all` immediately fails, leaving the other promises unresolved. If one of those later throws an unhandled exception, it can crash your Node.js process. Instead, prefer `Promise.allSettled`, which safely waits for all promises to complete and reports their outcomes.
|
|
425
|
-
|
|
426
|
-
## Workflow Timeouts
|
|
427
|
-
|
|
428
|
-
You can set a timeout for a workflow by passing a `timeoutMS` argument to `SolidActions.startWorkflow`.
|
|
429
|
-
When the timeout expires, the workflow **and all its children** are cancelled.
|
|
430
|
-
Cancelling a workflow sets its status to `CANCELLED` and preempts its execution at the beginning of its next step.
|
|
431
|
-
|
|
432
|
-
Timeouts are **start-to-completion**: a workflow's timeout does not begin until the workflow starts execution.
|
|
433
|
-
Also, timeouts are **durable**: they are stored in the database and persist across restarts, so workflows can have very long timeouts.
|
|
434
|
-
|
|
435
|
-
Example syntax:
|
|
436
|
-
|
|
437
|
-
```javascript
|
|
438
|
-
async function taskFunction(task) {
|
|
439
|
-
// ...
|
|
440
|
-
}
|
|
441
|
-
const taskWorkflow = SolidActions.registerWorkflow(taskFunction);
|
|
442
|
-
|
|
443
|
-
async function main() {
|
|
444
|
-
const task = ...
|
|
445
|
-
const timeout = ... // Timeout in milliseconds
|
|
446
|
-
const handle = await SolidActions.startWorkflow(taskWorkflow, {timeoutMS: timeout})(task);
|
|
447
|
-
}
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
## Durable Sleep
|
|
451
|
-
|
|
452
|
-
You can use `SolidActions.sleep()` to put your workflow to sleep for any period of time.
|
|
453
|
-
This sleep is **durable**—SolidActions saves the wakeup time in the database so that even if the workflow is interrupted and restarted multiple times while sleeping, it still wakes up on schedule.
|
|
454
|
-
|
|
455
|
-
Sleeping is useful for scheduling a workflow to run in the future (even days, weeks, or months from now).
|
|
456
|
-
For example:
|
|
457
|
-
|
|
458
|
-
```javascript
|
|
459
|
-
@SolidActions.workflow()
|
|
460
|
-
static async exampleWorkflow(timeToSleep, task) {
|
|
461
|
-
await SolidActions.sleep(timeToSleep);
|
|
462
|
-
await runTask(task);
|
|
463
|
-
}
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
## Debouncing Workflows
|
|
467
|
-
|
|
468
|
-
You can create a `Debouncer` to debounce your workflows.
|
|
469
|
-
Debouncing delays workflow execution until some time has passed since the workflow has last been called.
|
|
470
|
-
This is useful for preventing wasted work when a workflow may be triggered multiple times in quick succession.
|
|
471
|
-
For example, if a user is editing an input field, you can debounce their changes to execute a processing workflow only after they haven't edited the field for some time:
|
|
472
|
-
|
|
473
|
-
### Debouncer
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
new Debouncer<Args extends unknown[], Return>(
|
|
477
|
-
params: DebouncerConfig<Args, Return>
|
|
478
|
-
)
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
```typescript
|
|
482
|
-
interface DebouncerConfig<Args extends unknown[], Return> {
|
|
483
|
-
workflow: (...args: Args) => Promise<Return>;
|
|
484
|
-
startWorkflowParams?: StartWorkflowParams;
|
|
485
|
-
debounceTimeoutMs?: number;
|
|
486
|
-
}
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
**Parameters:**
|
|
490
|
-
|
|
491
|
-
- **workflow**: The workflow to debounce. Note that workflows from configured instances cannot be debounced.
|
|
492
|
-
- **startWorkflowParams**: Optional workflow parameters, as in `startWorkflow`. Applied to all workflows started from this debouncer.
|
|
493
|
-
- **debounceTimeoutMs**: After this time elapses since the first time a workflow is submitted from this debouncer, the workflow is started regardless of the debounce period.
|
|
494
|
-
|
|
495
|
-
### debouncer.debounce
|
|
496
|
-
|
|
497
|
-
```typescript
|
|
498
|
-
debouncer.debounce(
|
|
499
|
-
debounceKey: string,
|
|
500
|
-
debouncePeriodMs: number,
|
|
501
|
-
...args: Args
|
|
502
|
-
): Promise<WorkflowHandle<Return>>
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
Submit a workflow for execution but delay it by `debouncePeriodMs`.
|
|
506
|
-
Returns a handle to the workflow.
|
|
507
|
-
The workflow may be debounced again, which further delays its execution (up to `debounceTimeoutMs`).
|
|
508
|
-
When the workflow eventually executes, it uses the **last** set of inputs passed into `debounce`.
|
|
509
|
-
After the workflow begins execution, the next call to `debounce` starts the debouncing process again for a new workflow execution.
|
|
510
|
-
|
|
511
|
-
**Parameters:**
|
|
512
|
-
|
|
513
|
-
- **debounceKey**: A key used to group workflow executions that will be debounced together. For example, if the debounce key is set to customer ID, each customer's workflows would be debounced separately.
|
|
514
|
-
- **debouncePeriodMs**: Delay this workflow's execution by this period in milliseconds.
|
|
515
|
-
- **...args**: Variadic workflow arguments.
|
|
516
|
-
|
|
517
|
-
**Example Syntax**:
|
|
518
|
-
|
|
519
|
-
```typescript
|
|
520
|
-
async function processInput(userInput: string) {
|
|
521
|
-
...
|
|
522
|
-
}
|
|
523
|
-
const processInputWorkflow = SolidActions.registerWorkflow(processInput);
|
|
524
|
-
|
|
525
|
-
// Each time a user submits a new input, debounce the processInput workflow.
|
|
526
|
-
// The workflow will wait until 60 seconds after the user stops submitting new inputs,
|
|
527
|
-
// then process the last input submitted.
|
|
528
|
-
const debouncer = new Debouncer({
|
|
529
|
-
workflow: processInputWorkflow,
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
async function onUserInputSubmit(userId: string, userInput: string) {
|
|
533
|
-
const debounceKey = userId;
|
|
534
|
-
const debouncePeriodMs = 60000; // 60 seconds
|
|
535
|
-
await debouncer.debounce(debounceKey, debouncePeriodMs, userInput);
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
## Workflow Versioning and Recovery
|
|
540
|
-
|
|
541
|
-
Because SolidActions recovers workflows by re-executing them using information saved in the database, a workflow cannot safely be recovered if its code has changed since the workflow was started.
|
|
542
|
-
To guard against this, SolidActions _versions_ applications and their workflows.
|
|
543
|
-
When SolidActions is launched, it computes an application version from a hash of the source code of its workflows (this can be overridden through the `applicationVersion`) configuration parameter.
|
|
544
|
-
All workflows are tagged with the application version on which they started.
|
|
545
|
-
|
|
546
|
-
When SolidActions tries to recover workflows, it only recovers workflows whose version matches the current application version.
|
|
547
|
-
This prevents unsafe recovery of workflows that depend on different code.
|
|
548
|
-
You cannot change the version of a workflow, but you can use `SolidActions.forkWorkflow` to restart a workflow from a specific step on a specific code version.
|
|
549
|
-
|
|
550
|
-
## Workflow Communication
|
|
551
|
-
|
|
552
|
-
SolidActions provides a few different ways to communicate with your workflows.
|
|
553
|
-
You can:
|
|
554
|
-
|
|
555
|
-
- Send messages to workflows
|
|
556
|
-
- Publish events from workflows for clients to read
|
|
557
|
-
- Stream values from workflows to clients
|
|
558
|
-
|
|
559
|
-
## Workflow Messaging and Notifications
|
|
560
|
-
|
|
561
|
-
You can send messages to a specific workflow.
|
|
562
|
-
This is useful for signaling a workflow or sending notifications to it while it's running.
|
|
563
|
-
|
|
564
|
-
<img src={require('@site/static/img/workflow-communication/workflow-messages.png').default} alt="SolidActions Steps" width="750" className="custom-img"/>
|
|
565
|
-
|
|
566
|
-
### Send
|
|
567
|
-
|
|
568
|
-
```typescript
|
|
569
|
-
SolidActions.send<T>(destinationID: string, message: T, topic?: string): Promise<void>;
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
You can call `SolidActions.send()` to send a message to a workflow.
|
|
573
|
-
Messages can optionally be associated with a topic and are queued on the receiver per topic.
|
|
574
|
-
|
|
575
|
-
You can also call `send` from outside of your SolidActions application with the SolidActions Client.
|
|
576
|
-
|
|
577
|
-
### Recv
|
|
578
|
-
|
|
579
|
-
```typescript
|
|
580
|
-
SolidActions.recv<T>(topic?: string, timeoutSeconds?: number): Promise<T | null>
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
Workflows can call `SolidActions.recv()` to receive messages sent to them, optionally for a particular topic.
|
|
584
|
-
Each call to `recv()` waits for and consumes the next message to arrive in the queue for the specified topic, returning `null` if the wait times out.
|
|
585
|
-
If the topic is not specified, this method only receives messages sent without a topic.
|
|
586
|
-
|
|
587
|
-
### Messages Example
|
|
588
|
-
|
|
589
|
-
Messages are especially useful for sending notifications to a workflow.
|
|
590
|
-
For example, in the e-commerce demo, the checkout workflow, after redirecting customers to a secure payments service, must wait for a notification from that service that the payment has finished processing.
|
|
591
|
-
|
|
592
|
-
To wait for this notification, the payments workflow uses `recv()`, executing failure-handling code if the notification doesn't arrive in time:
|
|
593
|
-
|
|
594
|
-
```javascript
|
|
595
|
-
@SolidActions.workflow()
|
|
596
|
-
static async checkoutWorkflow(...): Promise<void> {
|
|
597
|
-
...
|
|
598
|
-
const notification = await SolidActions.recv<string>(PAYMENT_STATUS, timeout);
|
|
599
|
-
if (notification) {
|
|
600
|
-
... // Handle the notification.
|
|
601
|
-
} else {
|
|
602
|
-
... // Handle a timeout.
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
A webhook waits for the payment processor to send the notification, then uses `send()` to forward it to the workflow:
|
|
608
|
-
|
|
609
|
-
```javascript
|
|
610
|
-
static async paymentWebhook(): Promise<void> {
|
|
611
|
-
const notificationMessage = ... // Parse the notification.
|
|
612
|
-
const workflowID = ... // Retrieve the workflow ID from notification metadata.
|
|
613
|
-
await SolidActions.send(workflowID, notificationMessage, PAYMENT_STATUS);
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
### Reliability Guarantees
|
|
618
|
-
|
|
619
|
-
All messages are persisted to the database, so if `send` completes successfully, the destination workflow is guaranteed to be able to `recv` it.
|
|
620
|
-
If you're sending a message from a workflow, SolidActions guarantees exactly-once delivery.
|
|
621
|
-
If you're sending a message from normal TypeScript code, you can specify an idempotency key for `send` to guarantee exactly-once delivery.
|
|
622
|
-
|
|
623
|
-
## Workflow Events
|
|
624
|
-
|
|
625
|
-
Workflows can publish _events_, which are key-value pairs associated with the workflow.
|
|
626
|
-
They are useful for publishing information about the status of a workflow or to send a result to clients while the workflow is running.
|
|
627
|
-
|
|
628
|
-
<img src={require('@site/static/img/workflow-communication/workflow-events.png').default} alt="SolidActions Steps" width="750" className="custom-img"/>
|
|
629
|
-
|
|
630
|
-
### setEvent
|
|
631
|
-
|
|
632
|
-
```typescript
|
|
633
|
-
SolidActions.setEvent<T>(key: string, value: T): Promise<void>
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
Any workflow can call `SolidActions.setEvent` to publish a key-value pair, or update its value if has already been published.
|
|
637
|
-
|
|
638
|
-
### getEvent
|
|
639
|
-
|
|
640
|
-
```typescript
|
|
641
|
-
SolidActions.getEvent<T>(workflowID: string, key: string, timeoutSeconds?: number): Promise<T | null>
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
You can call `SolidActions.getEvent` to retrieve the value published by a particular workflow ID for a particular key.
|
|
645
|
-
If the event does not yet exist, this call waits for it to be published, returning `null` if the wait times out.
|
|
646
|
-
|
|
647
|
-
You can also call `getEvent` from outside of your SolidActions application with SolidActions Client.
|
|
648
|
-
|
|
649
|
-
### Events Example
|
|
650
|
-
|
|
651
|
-
Events are especially useful for writing interactive workflows that communicate information to their caller.
|
|
652
|
-
For example, in the e-commerce demo, the checkout workflow, after validating an order, directs the customer to a secure payments service to handle credit card processing.
|
|
653
|
-
To communicate the payments URL to the customer, it uses events.
|
|
654
|
-
|
|
655
|
-
The checkout workflow emits the payments URL using `setEvent()`:
|
|
656
|
-
|
|
657
|
-
```javascript
|
|
658
|
-
@SolidActions.workflow()
|
|
659
|
-
static async checkoutWorkflow(...): Promise<void> {
|
|
660
|
-
...
|
|
661
|
-
const paymentsURL = ...
|
|
662
|
-
await SolidActions.setEvent(PAYMENT_URL, paymentsURL);
|
|
663
|
-
...
|
|
664
|
-
}
|
|
665
|
-
```
|
|
666
|
-
|
|
667
|
-
The HTTP handler that originally started the workflow uses `getEvent()` to await this URL, then redirects the customer to it:
|
|
668
|
-
|
|
669
|
-
```javascript
|
|
670
|
-
static async webCheckout(...): Promise<void> {
|
|
671
|
-
const handle = await SolidActions.startWorkflow(Shop).checkoutWorkflow(...);
|
|
672
|
-
const url = await SolidActions.getEvent<string>(handle.workflowID, PAYMENT_URL);
|
|
673
|
-
if (url === null) {
|
|
674
|
-
SolidActions.koaContext.redirect(`${origin}/checkout/cancel`);
|
|
675
|
-
} else {
|
|
676
|
-
SolidActions.koaContext.redirect(url);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
### Reliability Guarantees
|
|
682
|
-
|
|
683
|
-
All events are persisted to the database, so the latest version of an event is always retrievable.
|
|
684
|
-
Additionally, if `getEvent` is called in a workflow, the retrieved value is persisted in the database so workflow recovery can use that value, even if the event is later updated.
|
|
685
|
-
|
|
686
|
-
## Workflow Streaming
|
|
687
|
-
|
|
688
|
-
Workflows can stream data in real time to clients.
|
|
689
|
-
This is useful for streaming results from a long-running workflow or LLM call or for monitoring or progress reporting.
|
|
690
|
-
|
|
691
|
-
<img src={require('@site/static/img/workflow-communication/workflow-streams.png').default} alt="SolidActions Steps" width="750" className="custom-img"/>
|
|
692
|
-
|
|
693
|
-
### Writing to Streams
|
|
694
|
-
|
|
695
|
-
```typescript
|
|
696
|
-
SolidActions.writeStream<T>(key: string, value: T): Promise<void>
|
|
697
|
-
```
|
|
698
|
-
|
|
699
|
-
You can write values to a stream from a workflow or its steps using `SolidActions.writeStream`.
|
|
700
|
-
A workflow may have any number of streams, each identified by a unique key.
|
|
701
|
-
|
|
702
|
-
When you are done writing to a stream, you should close it with `SolidActions.closeStream`.
|
|
703
|
-
Otherwise, streams are automatically closed when the workflow terminates.
|
|
704
|
-
|
|
705
|
-
```typescript
|
|
706
|
-
SolidActions.closeStream(key: string): Promise<void>
|
|
707
|
-
```
|
|
708
|
-
|
|
709
|
-
SolidActions streams are immutable and append-only.
|
|
710
|
-
Writes to a stream from a workflow happen exactly-once.
|
|
711
|
-
Writes to a stream from a step happen at-least-once; if a step fails and is retried, it may write to the stream multiple times.
|
|
712
|
-
Readers will see all values written to the stream from all tries of the step in the order in which they were written.
|
|
713
|
-
|
|
714
|
-
**Example syntax:**
|
|
715
|
-
|
|
716
|
-
```typescript
|
|
717
|
-
async function producerWorkflowFunction() {
|
|
718
|
-
await SolidActions.writeStream('example_key', { step: 1, data: 'value1' });
|
|
719
|
-
await SolidActions.writeStream('example_key', { step: 2, data: 'value2' });
|
|
720
|
-
await SolidActions.closeStream('example_key'); // Signal completion
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
const producerWorkflow = SolidActions.registerWorkflow(producerWorkflowFunction);
|
|
724
|
-
```
|
|
725
|
-
|
|
726
|
-
### Reading from Streams
|
|
727
|
-
|
|
728
|
-
```typescript
|
|
729
|
-
SolidActions.readStream<T>(workflowID: string, key: string): AsyncGenerator<T, void, unknown>
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
You can read values from a stream from anywhere using `SolidActions.readStream`.
|
|
733
|
-
This function reads values from a stream identified by a workflow ID and key, yielding each value in order until the stream is closed or the workflow terminates.
|
|
734
|
-
|
|
735
|
-
You can also read from a stream from outside a SolidActions application with a SolidActions Client.
|
|
736
|
-
|
|
737
|
-
**Example syntax:**
|
|
738
|
-
|
|
739
|
-
```typescript
|
|
740
|
-
for await (const value of SolidActions.readStream(workflowID, 'example_key')) {
|
|
741
|
-
console.log(`Received: ${JSON.stringify(value)}`);
|
|
742
|
-
}
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
## Steps
|
|
746
|
-
|
|
747
|
-
When using SolidActions workflows, you should call any function that performs complex operations or accesses external APIs or services as a _step_.
|
|
748
|
-
If a workflow is interrupted, upon restart it automatically resumes execution from the **last completed step**.
|
|
749
|
-
|
|
750
|
-
You can use `SolidActions.runStep` to call a function as a step. For a function to be used as a step, it should have a return value that can be serialized as JSON, and should not have non-durable side effects. ALWAYS call steps this way unless otherwise specify.
|
|
751
|
-
|
|
752
|
-
```javascript
|
|
753
|
-
async function generateRandomNumber() {
|
|
754
|
-
return Math.random();
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
async function workflowFunction() {
|
|
758
|
-
const randomNumber = await SolidActions.runStep(() => generateRandomNumber(), { name: 'generateRandomNumber' });
|
|
759
|
-
}
|
|
760
|
-
const workflow = SolidActions.registerWorkflow(workflowFunction);
|
|
761
|
-
```
|
|
762
|
-
|
|
763
|
-
Alternatively, you can register a function as a step using `SolidActions.registerStep`:
|
|
764
|
-
NEVER do this unless specifically asked, ALWAYS use SolidActions.runStep instead.
|
|
765
|
-
|
|
766
|
-
```javascript
|
|
767
|
-
async function generateRandomNumber() {
|
|
768
|
-
return Math.random();
|
|
769
|
-
}
|
|
770
|
-
const randomStep = SolidActions.registerStep(generateRandomNumber);
|
|
771
|
-
|
|
772
|
-
async function workflowFunction() {
|
|
773
|
-
const randomNumber = await randomStep();
|
|
774
|
-
}
|
|
775
|
-
const workflow = SolidActions.registerWorkflow(workflowFunction);
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
Or use the `@SolidActions.step()` decorator:
|
|
779
|
-
NEVER do this unless specifically asked, ALWAYS use SolidActions.runStep instead.
|
|
780
|
-
|
|
781
|
-
```typescript
|
|
782
|
-
export class Example {
|
|
783
|
-
@SolidActions.step()
|
|
784
|
-
static async generateRandomNumber() {
|
|
785
|
-
return Math.random();
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
@SolidActions.workflow()
|
|
789
|
-
static async exampleWorkflow() {
|
|
790
|
-
await Example.generateRandomNumber();
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
### Configurable Retries
|
|
796
|
-
|
|
797
|
-
You can optionally configure a step to automatically retry any exception a set number of times with exponential backoff.
|
|
798
|
-
This is useful for automatically handling transient failures, like making requests to unreliable APIs.
|
|
799
|
-
Retries are configurable through arguments to the step decorator:
|
|
800
|
-
|
|
801
|
-
```typescript
|
|
802
|
-
export interface StepConfig {
|
|
803
|
-
retriesAllowed?: boolean; // Should failures be retried? (default false)
|
|
804
|
-
intervalSeconds?: number; // Seconds to wait before the first retry attempt (default 1).
|
|
805
|
-
maxAttempts?: number; // Maximum number of retry attempts (default 3). If errors occur more times than this, throw an exception.
|
|
806
|
-
backoffRate?: number; // Multiplier by which the retry interval increases after a retry attempt (default 2).
|
|
807
|
-
}
|
|
808
|
-
```
|
|
809
|
-
|
|
810
|
-
For example, let's configure this step to retry exceptions (such as if `example.com` is temporarily down) up to 10 times:
|
|
811
|
-
|
|
812
|
-
```javascript
|
|
813
|
-
async function fetchFunction() {
|
|
814
|
-
return await fetch('https://example.com').then((r) => r.text());
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
async function workflowFunction() {
|
|
818
|
-
const randomNumber = await SolidActions.runStep(() => fetchFunction(), {
|
|
819
|
-
name: 'fetchFunction',
|
|
820
|
-
retriesAllowed: true,
|
|
821
|
-
maxAttempts: 10,
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
Or if registering the step:
|
|
827
|
-
|
|
828
|
-
```javascript
|
|
829
|
-
async function fetchFunction() {
|
|
830
|
-
return await fetch('https://example.com').then((r) => r.text());
|
|
831
|
-
}
|
|
832
|
-
const fetchStep = SolidActions.registerStep(fetchFunction, {
|
|
833
|
-
retriesAllowed: true,
|
|
834
|
-
maxAttempts: 10,
|
|
835
|
-
});
|
|
836
|
-
```
|
|
837
|
-
|
|
838
|
-
Or if using decorators:
|
|
839
|
-
|
|
840
|
-
```javascript
|
|
841
|
-
@SolidActions.step({retriesAllowed: true, maxAttempts: 10})
|
|
842
|
-
static async exampleStep() {
|
|
843
|
-
return await fetch("https://example.com").then(r => r.text());
|
|
844
|
-
}
|
|
845
|
-
```
|
|
846
|
-
|
|
847
|
-
## Queues
|
|
848
|
-
|
|
849
|
-
You can use queues to run many workflows at once with managed concurrency.
|
|
850
|
-
Queues provide _flow control_, letting you manage how many workflows run at once or how often workflows are started.
|
|
851
|
-
|
|
852
|
-
To create a queue, specify its name:
|
|
853
|
-
|
|
854
|
-
```javascript
|
|
855
|
-
import { SolidActions, WorkflowQueue } from '@dbos-inc/dbos-sdk';
|
|
856
|
-
|
|
857
|
-
const queue = new WorkflowQueue('example_queue');
|
|
858
|
-
```
|
|
859
|
-
|
|
860
|
-
You can then enqueue any workflow by passing the queue as an argument to `SolidActions.startWorkflow`.
|
|
861
|
-
Enqueuing a function submits it for execution and returns a handle to it.
|
|
862
|
-
Queued tasks are started in first-in, first-out (FIFO) order.
|
|
863
|
-
|
|
864
|
-
```javascript
|
|
865
|
-
const queue = new WorkflowQueue("example_queue");
|
|
866
|
-
|
|
867
|
-
class Tasks {
|
|
868
|
-
@SolidActions.workflow()
|
|
869
|
-
static async processTask(task) {
|
|
870
|
-
// ...
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
async function main() {
|
|
875
|
-
const task = ...
|
|
876
|
-
const handle = await SolidActions.startWorkflow(Tasks, {queueName: queue.name}).processTask(task)
|
|
877
|
-
}
|
|
878
|
-
```
|
|
879
|
-
|
|
880
|
-
### Queue Example
|
|
881
|
-
|
|
882
|
-
Here's an example of a workflow using a queue to process tasks in parallel:
|
|
883
|
-
|
|
884
|
-
```javascript
|
|
885
|
-
import { SolidActions, WorkflowQueue } from '@dbos-inc/dbos-sdk';
|
|
886
|
-
|
|
887
|
-
const queue = new WorkflowQueue('example_queue');
|
|
888
|
-
|
|
889
|
-
async function taskFunction(task) {
|
|
890
|
-
// ...
|
|
891
|
-
}
|
|
892
|
-
const taskWorkflow = SolidActions.registerWorkflow(taskFunction, { name: 'taskWorkflow' });
|
|
893
|
-
|
|
894
|
-
async function queueFunction(tasks) {
|
|
895
|
-
const handles = [];
|
|
896
|
-
|
|
897
|
-
// Enqueue each task so all tasks are processed concurrently.
|
|
898
|
-
for (const task of tasks) {
|
|
899
|
-
handles.push(await SolidActions.startWorkflow(taskWorkflow, { queueName: queue.name })(task));
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// Wait for each task to complete and retrieve its result.
|
|
903
|
-
// Return the results of all tasks.
|
|
904
|
-
const results = [];
|
|
905
|
-
for (const h of handles) {
|
|
906
|
-
results.push(await h.getResult());
|
|
907
|
-
}
|
|
908
|
-
return results;
|
|
909
|
-
}
|
|
910
|
-
const queueWorkflow = SolidActions.registerWorkflow(queueFunction, { name: 'queueWorkflow' });
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
### Enqueueing from Another Application
|
|
914
|
-
|
|
915
|
-
Often, you want to enqueue a workflow from outside your SolidActions application.
|
|
916
|
-
For example, let's say you have an API server and a data processing service.
|
|
917
|
-
You're using SolidActions to build a durable data pipeline in the data processing service.
|
|
918
|
-
When the API server receives a request, it should enqueue the data pipeline for execution on the data processing service.
|
|
919
|
-
|
|
920
|
-
You can use the SolidActions Client to enqueue workflows from outside your SolidActions application by connecting directly to your SolidActions application's system database.
|
|
921
|
-
Since the SolidActions Client is designed to be used from outside your SolidActions application, workflow and queue metadata must be specified explicitly.
|
|
922
|
-
|
|
923
|
-
For example, this code enqueues the `dataPipeline` workflow on the `pipelineQueue` queue with `task` as an argument.
|
|
924
|
-
|
|
925
|
-
```ts
|
|
926
|
-
import { SolidActionsClient } from '@dbos-inc/dbos-sdk';
|
|
927
|
-
|
|
928
|
-
const client = await SolidActionsClient.create({ systemDatabaseUrl: process.env.SolidActions_SYSTEM_DATABASE_URL });
|
|
929
|
-
|
|
930
|
-
type ProcessTask = typeof Tasks.processTask;
|
|
931
|
-
await client.enqueue<ProcessTask>(
|
|
932
|
-
{
|
|
933
|
-
workflowName: 'dataPipeline',
|
|
934
|
-
queueName: 'pipelineQueue',
|
|
935
|
-
},
|
|
936
|
-
task,
|
|
937
|
-
);
|
|
938
|
-
```
|
|
939
|
-
|
|
940
|
-
### Managing Concurrency
|
|
941
|
-
|
|
942
|
-
You can control how many workflows from a queue run simultaneously by configuring concurrency limits.
|
|
943
|
-
This helps prevent resource exhaustion when workflows consume significant memory or processing power.
|
|
944
|
-
|
|
945
|
-
#### Worker Concurrency
|
|
946
|
-
|
|
947
|
-
Worker concurrency sets the maximum number of workflows from a queue that can run concurrently on a single SolidActions process.
|
|
948
|
-
This is particularly useful for resource-intensive workflows to avoid exhausting the resources of any process.
|
|
949
|
-
For example, this queue has a worker concurrency of 5, so each process will run at most 5 workflows from this queue simultaneously:
|
|
950
|
-
|
|
951
|
-
```javascript
|
|
952
|
-
import { SolidActions, WorkflowQueue } from '@dbos-inc/dbos-sdk';
|
|
953
|
-
|
|
954
|
-
const queue = new WorkflowQueue('example_queue', { workerConcurrency: 5 });
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
#### Global Concurrency
|
|
958
|
-
|
|
959
|
-
Global concurrency limits the total number of workflows from a queue that can run concurrently across all SolidActions processes in your application.
|
|
960
|
-
For example, this queue will have a maximum of 10 workflows running simultaneously across your entire application.
|
|
961
|
-
|
|
962
|
-
:::warning
|
|
963
|
-
Worker concurrency limits are recommended for most use cases.
|
|
964
|
-
Take care when using a global concurrency limit as any `PENDING` workflow on the queue counts toward the limit, including workflows from previous application versions
|
|
965
|
-
:::
|
|
966
|
-
|
|
967
|
-
```javascript
|
|
968
|
-
import { SolidActions, WorkflowQueue } from '@dbos-inc/dbos-sdk';
|
|
969
|
-
|
|
970
|
-
const queue = new WorkflowQueue('example_queue', { concurrency: 10 });
|
|
971
|
-
```
|
|
972
|
-
|
|
973
|
-
#### In-Order Processing
|
|
974
|
-
|
|
975
|
-
You can use a queue with `concurrency=1` to guarantee sequential, in-order processing of events.
|
|
976
|
-
Only a single event will be processed at a time.
|
|
977
|
-
For example, this app processes events sequentially in the order of their arrival:
|
|
978
|
-
|
|
979
|
-
```javascript
|
|
980
|
-
import { SolidActions, WorkflowQueue } from '@dbos-inc/dbos-sdk';
|
|
981
|
-
import express from 'express';
|
|
982
|
-
|
|
983
|
-
const serialQueue = new WorkflowQueue('in_order_queue', { concurrency: 1 });
|
|
984
|
-
const app = express();
|
|
985
|
-
|
|
986
|
-
class Tasks {
|
|
987
|
-
@SolidActions.workflow()
|
|
988
|
-
static async processTask(task) {
|
|
989
|
-
// ... process task
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
app.get('/events/:event', async (req, res) => {
|
|
994
|
-
await SolidActions.startWorkflow(Tasks, { queueName: serialQueue.name }).processTask(req.params);
|
|
995
|
-
await res.send('Workflow Started!');
|
|
996
|
-
});
|
|
997
|
-
|
|
998
|
-
// Launch SolidActions and start the server
|
|
999
|
-
async function main() {
|
|
1000
|
-
await SolidActions.launch();
|
|
1001
|
-
app.listen(3000, () => {});
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
main().catch(console.log);
|
|
1005
|
-
```
|
|
1006
|
-
|
|
1007
|
-
### Rate Limiting
|
|
1008
|
-
|
|
1009
|
-
You can set _rate limits_ for a queue, limiting the number of functions that it can start in a given period.
|
|
1010
|
-
Rate limits are global across all SolidActions processes using this queue.
|
|
1011
|
-
For example, this queue has a limit of 50 with a period of 30 seconds, so it may not start more than 50 functions in 30 seconds:
|
|
1012
|
-
|
|
1013
|
-
```javascript
|
|
1014
|
-
const queue = new WorkflowQueue('example_queue', { rateLimit: { limitPerPeriod: 50, periodSec: 30 } });
|
|
1015
|
-
```
|
|
1016
|
-
|
|
1017
|
-
Rate limits are especially useful when working with a rate-limited API, such as many LLM APIs.
|
|
1018
|
-
|
|
1019
|
-
### Setting Timeouts
|
|
1020
|
-
|
|
1021
|
-
You can set a timeout for an enqueued workflow by passing a `timeoutMS` argument to `SolidActions.startWorkflow`.
|
|
1022
|
-
When the timeout expires, the workflow **and all its children** are cancelled.
|
|
1023
|
-
Cancelling a workflow sets its status to `CANCELLED` and preempts its execution at the beginning of its next step.
|
|
1024
|
-
|
|
1025
|
-
Timeouts are **start-to-completion**: a workflow's timeout does not begin until the workflow is dequeued and starts execution.
|
|
1026
|
-
Also, timeouts are **durable**: they are stored in the database and persist across restarts, so workflows can have very long timeouts.
|
|
1027
|
-
|
|
1028
|
-
Example syntax:
|
|
1029
|
-
|
|
1030
|
-
```javascript
|
|
1031
|
-
const queue = new WorkflowQueue("example_queue");
|
|
1032
|
-
|
|
1033
|
-
async function taskFunction(task) {
|
|
1034
|
-
// ...
|
|
1035
|
-
}
|
|
1036
|
-
const taskWorkflow = SolidActions.registerWorkflow(taskFunction, {"name": "taskWorkflow"});
|
|
1037
|
-
|
|
1038
|
-
async function main() {
|
|
1039
|
-
const task = ...
|
|
1040
|
-
const timeout = ... // Timeout in milliseconds
|
|
1041
|
-
const handle = await SolidActions.startWorkflow(taskWorkflow, {queueName: queue.name, timeoutMS: timeout})(task);
|
|
1042
|
-
}
|
|
1043
|
-
```
|
|
1044
|
-
|
|
1045
|
-
### Partitioning Queues
|
|
1046
|
-
|
|
1047
|
-
You can **partition** queues to distribute work across dynamically created queue partitions.
|
|
1048
|
-
When you enqueue a workflow on a partitioned queue, you must supply a queue partition key.
|
|
1049
|
-
Partitioned queues dequeue workflows and apply flow control limits for individual partitions, not for the entire queue.
|
|
1050
|
-
Essentially, you can think of each partition as a "subqueue" you dynamically create by enqueueing a workflow with a partition key.
|
|
1051
|
-
|
|
1052
|
-
For example, suppose you want your users to each be able to run at most one task at a time.
|
|
1053
|
-
You can do this with a partitioned queue with a maximum concurrency limit of 1 where the partition key is user ID.
|
|
1054
|
-
|
|
1055
|
-
**Example Syntax**
|
|
1056
|
-
|
|
1057
|
-
```ts
|
|
1058
|
-
const queue = new WorkflowQueue('example_queue', { partitionQueue: true, concurrency: 1 });
|
|
1059
|
-
|
|
1060
|
-
async function onUserTaskSubmission(userID: string, task: Task) {
|
|
1061
|
-
// Partition the task queue by user ID. As the queue has a
|
|
1062
|
-
// maximum concurrency of 1, this means that at most one
|
|
1063
|
-
// task can run at once per user (but tasks from different
|
|
1064
|
-
// users can run concurrently).
|
|
1065
|
-
await SolidActions.startWorkflow(taskWorkflow, {
|
|
1066
|
-
queueName: queue.name,
|
|
1067
|
-
enqueueOptions: { queuePartitionKey: userID },
|
|
1068
|
-
})(task);
|
|
1069
|
-
}
|
|
1070
|
-
```
|
|
1071
|
-
|
|
1072
|
-
### Deduplication
|
|
1073
|
-
|
|
1074
|
-
You can set a deduplication ID for an enqueued workflow as an argument to `SolidActions.startWorkflow`.
|
|
1075
|
-
At any given time, only one workflow with a specific deduplication ID can be enqueued in the specified queue.
|
|
1076
|
-
If a workflow with a deduplication ID is currently enqueued or actively executing (status `ENQUEUED` or `PENDING`), subsequent workflow enqueue attempt with the same deduplication ID in the same queue will raise a `SolidActionsQueueDuplicatedError` exception.
|
|
1077
|
-
|
|
1078
|
-
For example, this is useful if you only want to have one workflow active at a time per user—set the deduplication ID to the user's ID.
|
|
1079
|
-
|
|
1080
|
-
Example syntax:
|
|
1081
|
-
|
|
1082
|
-
```javascript
|
|
1083
|
-
const queue = new WorkflowQueue("example_queue");
|
|
1084
|
-
|
|
1085
|
-
async function taskFunction(task) {
|
|
1086
|
-
// ...
|
|
1087
|
-
}
|
|
1088
|
-
const taskWorkflow = SolidActions.registerWorkflow(taskFunction, {"name": "taskWorkflow"});
|
|
1089
|
-
|
|
1090
|
-
async function main() {
|
|
1091
|
-
const task = ...
|
|
1092
|
-
const dedup: string = ...
|
|
1093
|
-
try {
|
|
1094
|
-
const handle = await SolidActions.startWorkflow(taskWorkflow, {queueName: queue.name, enqueueOptions: {deduplicationID: dedup}})(task);
|
|
1095
|
-
} catch (e) {
|
|
1096
|
-
// Handle SolidActionsQueueDuplicatedError
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
```
|
|
1100
|
-
|
|
1101
|
-
### Priority
|
|
1102
|
-
|
|
1103
|
-
You can set a priority for an enqueued workflow as an argument to `SolidActions.startWorkflow`.
|
|
1104
|
-
Workflows with the same priority are dequeued in **FIFO (first in, first out)** order. Priority values can range from `1` to `2,147,483,647`, where **a low number indicates a higher priority**.
|
|
1105
|
-
If using priority, you must set `usePriority: true` on your queue.
|
|
1106
|
-
|
|
1107
|
-
:::tip
|
|
1108
|
-
Workflows without assigned priorities have the highest priority and are dequeued before workflows with assigned priorities.
|
|
1109
|
-
:::
|
|
1110
|
-
|
|
1111
|
-
Example syntax:
|
|
1112
|
-
|
|
1113
|
-
```javascript
|
|
1114
|
-
const queue = new WorkflowQueue("example_queue", {usePriority: true});
|
|
1115
|
-
|
|
1116
|
-
async function taskFunction(task) {
|
|
1117
|
-
// ...
|
|
1118
|
-
}
|
|
1119
|
-
const taskWorkflow = SolidActions.registerWorkflow(taskFunction, {"name": "taskWorkflow"});
|
|
1120
|
-
|
|
1121
|
-
async function main() {
|
|
1122
|
-
const task = ...
|
|
1123
|
-
const priority: number = ...
|
|
1124
|
-
const handle = await SolidActions.startWorkflow(taskWorkflow, {queueName: queue.name, enqueueOptions: {priority: priority}})(task);
|
|
1125
|
-
}
|
|
1126
|
-
```
|
|
1127
|
-
|
|
1128
|
-
## Classes
|
|
1129
|
-
|
|
1130
|
-
You can use class instance methods as workflows and steps.
|
|
1131
|
-
Any class instance method can be freely used as a step using the SolidActions.step decorator or SolidActions.runstep; there are no special requirements.
|
|
1132
|
-
To use a class instance method as a workflow, you must use the SolidActions.workflow decorator and the class must inherit from `ConfiguredInstance`.
|
|
1133
|
-
For example:
|
|
1134
|
-
|
|
1135
|
-
```typescript
|
|
1136
|
-
class MyClass extends ConfiguredInstance {
|
|
1137
|
-
cfg: MyConfig;
|
|
1138
|
-
constructor(name: string, config: MyConfig) {
|
|
1139
|
-
super(name);
|
|
1140
|
-
this.cfg = cfg;
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
override async initialize(): Promise<void> {
|
|
1144
|
-
// ... Validate this.cfg; will be called at SolidActions.launch()
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
@SolidActions.workflow()
|
|
1148
|
-
async testWorkflow(p: string): Promise<void> {
|
|
1149
|
-
// ... Operations that use this.cfg
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
const myClassInstance = new MyClass('instanceA');
|
|
1154
|
-
```
|
|
1155
|
-
|
|
1156
|
-
When you create a new instance of such a class, the constructor for the base `ConfiguredInstance` must be called with a `name`.
|
|
1157
|
-
This `name` should be unique among instances of the same class.
|
|
1158
|
-
Additionally, all `ConfiguredInstance` classes must be instantiated before SolidActions.launch() is called.
|
|
1159
|
-
|
|
1160
|
-
The reason for these requirements is to enable workflow recovery. When you create a new instance of, SolidActions stores it in a global registry indexed by `name`. When SolidActions needs to recover a workflow belonging to that class, it looks up the `name` so it can run the workflow using the right class instance. While names are used by SolidActions Transact internally to find the correct object instance across system restarts, they are also potentially useful for monitoring, tracing, and debugging.
|
|
1161
|
-
You should AVOID using ConfiguredInstance if at all possible and instead use registerWorkflow on regular non-class functions.
|
|
1162
|
-
|
|
1163
|
-
### Testing SolidActions Functions
|
|
1164
|
-
|
|
1165
|
-
Unless otherwise specified, recommend testing SolidActions applications with jest.
|
|
1166
|
-
|
|
1167
|
-
You MUST use this beforeAll to reset SolidActions between tests:
|
|
1168
|
-
|
|
1169
|
-
```javascript
|
|
1170
|
-
beforeAll(async () => {
|
|
1171
|
-
SolidActions.setConfig({
|
|
1172
|
-
name: 'my-app',
|
|
1173
|
-
databaseUrl: process.env.SolidActions_TESTING_DATABASE_URL,
|
|
1174
|
-
});
|
|
1175
|
-
await SolidActions.launch();
|
|
1176
|
-
});
|
|
1177
|
-
```
|
|
1178
|
-
|
|
1179
|
-
### Logging
|
|
1180
|
-
|
|
1181
|
-
ALWAYS log errors like this:
|
|
1182
|
-
|
|
1183
|
-
```typescript
|
|
1184
|
-
SolidActions.logger.error(`Error: ${(error as Error).message}`);
|
|
1185
|
-
```
|
|
1186
|
-
|
|
1187
|
-
## Workflow Handles
|
|
1188
|
-
|
|
1189
|
-
A workflow handle represents the state of a particular active or completed workflow execution.
|
|
1190
|
-
You obtain a workflow handle when using `SolidActions.startWorkflow` to start a workflow in the background.
|
|
1191
|
-
If you know a workflow's identity, you can also retrieve its handle using `SolidActions.retrieveWorkflow`.
|
|
1192
|
-
|
|
1193
|
-
Workflow handles have the following methods:
|
|
1194
|
-
|
|
1195
|
-
### handle.workflowID
|
|
1196
|
-
|
|
1197
|
-
```typescript
|
|
1198
|
-
handle.workflowID(): string;
|
|
1199
|
-
```
|
|
1200
|
-
|
|
1201
|
-
Retrieve the ID of the workflow.
|
|
1202
|
-
|
|
1203
|
-
### handle.getResult
|
|
1204
|
-
|
|
1205
|
-
```typescript
|
|
1206
|
-
handle.getResult(): Promise<R>;
|
|
1207
|
-
```
|
|
1208
|
-
|
|
1209
|
-
Wait for the workflow to complete, then return its result.
|
|
1210
|
-
|
|
1211
|
-
### handle.getStatus
|
|
1212
|
-
|
|
1213
|
-
```typescript
|
|
1214
|
-
handle.getStatus(): Promise<WorkflowStatus>;
|
|
1215
|
-
```
|
|
1216
|
-
|
|
1217
|
-
Retrieve the WorkflowStatus of the workflow:
|
|
1218
|
-
|
|
1219
|
-
### Workflow Status
|
|
1220
|
-
|
|
1221
|
-
Some workflow introspection and management methods return a `WorkflowStatus`.
|
|
1222
|
-
This object has the following definition:
|
|
1223
|
-
|
|
1224
|
-
```typescript
|
|
1225
|
-
export interface WorkflowStatus {
|
|
1226
|
-
// The workflow ID
|
|
1227
|
-
readonly workflowID: string;
|
|
1228
|
-
// The workflow status. Must be one of ENQUEUED, PENDING, SUCCESS, ERROR, CANCELLED, or RETRIES_EXCEEDED
|
|
1229
|
-
readonly status: string;
|
|
1230
|
-
// The name of the workflow function.
|
|
1231
|
-
readonly workflowName: string;
|
|
1232
|
-
// The name of the workflow's class, if any
|
|
1233
|
-
readonly workflowClassName: string; // The class name holding the workflow function.
|
|
1234
|
-
// The name with which the workflow's class instance was configured, if any.
|
|
1235
|
-
readonly workflowConfigName?: string;
|
|
1236
|
-
// If the workflow was enqueued, the name of the queue.
|
|
1237
|
-
readonly queueName?: string;
|
|
1238
|
-
// The workflow's output, if any.
|
|
1239
|
-
readonly output?: unknown;
|
|
1240
|
-
// The error thrown by the workflow, if any.
|
|
1241
|
-
readonly error?: unknown;
|
|
1242
|
-
// The deserialized workflow inputs.
|
|
1243
|
-
readonly input?: unknown[];
|
|
1244
|
-
// The ID of the executor (process) that most recently executed this workflow.
|
|
1245
|
-
readonly executorId?: string;
|
|
1246
|
-
// The application version on which this workflow started.
|
|
1247
|
-
readonly applicationVersion?: string;
|
|
1248
|
-
// The number of times this workflow has been started.
|
|
1249
|
-
readonly recoveryAttempts?: number;
|
|
1250
|
-
// Workflow start time, as a UNIX epoch timestamp in milliseconds
|
|
1251
|
-
readonly createdAt: number;
|
|
1252
|
-
// Last time the workflow status was updated, as a UNIX epoch timestamp in milliseconds. For a completed workflow, this is the workflow completion timestamp.
|
|
1253
|
-
readonly updatedAt?: number;
|
|
1254
|
-
// The timeout specified for this workflow, if any. Timeouts are start-to-close.
|
|
1255
|
-
readonly timeoutMS?: number | null;
|
|
1256
|
-
// The deadline at which this workflow times out, if any. Not set until the workflow begins execution.
|
|
1257
|
-
readonly deadlineEpochMS?: number;
|
|
1258
|
-
}
|
|
1259
|
-
```
|
|
1260
|
-
|
|
1261
|
-
## SolidActions Variables
|
|
1262
|
-
|
|
1263
|
-
### SolidActions.workflowID
|
|
1264
|
-
|
|
1265
|
-
```typescript
|
|
1266
|
-
SolidActions.workflowID: string | undefined;
|
|
1267
|
-
```
|
|
1268
|
-
|
|
1269
|
-
Return the ID of the current workflow, if in a workflow.
|
|
1270
|
-
|
|
1271
|
-
### SolidActions.stepID
|
|
1272
|
-
|
|
1273
|
-
```typescript
|
|
1274
|
-
SolidActions.stepID: string | undefined;
|
|
1275
|
-
```
|
|
1276
|
-
|
|
1277
|
-
Return the unique ID of the current step within a workflow.
|
|
1278
|
-
|
|
1279
|
-
### SolidActions.stepStatus
|
|
1280
|
-
|
|
1281
|
-
```typescript
|
|
1282
|
-
SolidActions.stepStatus: StepStatus | undefined;
|
|
1283
|
-
```
|
|
1284
|
-
|
|
1285
|
-
Return the status of the currently executing step.
|
|
1286
|
-
This object has the following properties:
|
|
1287
|
-
|
|
1288
|
-
```typescript
|
|
1289
|
-
interface StepStatus {
|
|
1290
|
-
// The unique ID of this step in its workflow.
|
|
1291
|
-
stepID: number;
|
|
1292
|
-
// For steps with automatic retries, which attempt number (zero-indexed) is currently executing.
|
|
1293
|
-
currentAttempt?: number;
|
|
1294
|
-
// For steps with automatic retries, the maximum number of attempts that will be made before the step fails.
|
|
1295
|
-
maxAttempts?: number;
|
|
1296
|
-
}
|
|
1297
|
-
```
|
|
1298
|
-
|
|
1299
|
-
## Workflow Management Methods
|
|
1300
|
-
|
|
1301
|
-
### SolidActions.listWorkflows
|
|
1302
|
-
|
|
1303
|
-
```typescript
|
|
1304
|
-
SolidActions.listWorkflows(
|
|
1305
|
-
input: GetWorkflowsInput
|
|
1306
|
-
): Promise<WorkflowStatus[]>
|
|
1307
|
-
```
|
|
1308
|
-
|
|
1309
|
-
```typescript
|
|
1310
|
-
interface GetWorkflowsInput {
|
|
1311
|
-
workflowIDs?: string[];
|
|
1312
|
-
workflowName?: string;
|
|
1313
|
-
status?: string;
|
|
1314
|
-
startTime?: string;
|
|
1315
|
-
endTime?: string;
|
|
1316
|
-
applicationVersion?: string;
|
|
1317
|
-
authenticatedUser?: string;
|
|
1318
|
-
limit?: number;
|
|
1319
|
-
offset?: number;
|
|
1320
|
-
sortDesc?: boolean;
|
|
1321
|
-
}
|
|
1322
|
-
```
|
|
1323
|
-
|
|
1324
|
-
Retrieve a list of WorkflowStatus of all workflows matching specified criteria.
|
|
1325
|
-
|
|
1326
|
-
**Parameters:**
|
|
1327
|
-
|
|
1328
|
-
- **workflowIDs**: Retrieve workflows with these IDs.
|
|
1329
|
-
- **workflowName**: Retrieve workflows with this name.
|
|
1330
|
-
- **status**: Retrieve workflows with this status (Must be `ENQUEUED`, `PENDING`, `SUCCESS`, `ERROR`, `CANCELLED`, or `RETRIES_EXCEEDED`)
|
|
1331
|
-
- **startTime**: Retrieve workflows started after this (RFC 3339-compliant) timestamp.
|
|
1332
|
-
- **endTime**: Retrieve workflows started before this (RFC 3339-compliant) timestamp.
|
|
1333
|
-
- **applicationVersion**: Retrieve workflows tagged with this application version.
|
|
1334
|
-
- **authenticatedUser**: Retrieve workflows run by this authenticated user.
|
|
1335
|
-
- **limit**: Retrieve up to this many workflows.
|
|
1336
|
-
- **offset**: Skip this many workflows from the results returned (for pagination).
|
|
1337
|
-
- **sortDesc**: Whether to sort the results in descending (`True`) or ascending (`False`) order by workflow start time.
|
|
1338
|
-
|
|
1339
|
-
### SolidActions.listQueuedWorkflows
|
|
1340
|
-
|
|
1341
|
-
```typescript
|
|
1342
|
-
SolidActions.listQueuedWorkflows(
|
|
1343
|
-
input: GetQueuedWorkflowsInput
|
|
1344
|
-
): Promise<WorkflowStatus[]>
|
|
1345
|
-
```
|
|
1346
|
-
|
|
1347
|
-
```typescript
|
|
1348
|
-
interface GetQueuedWorkflowsInput {
|
|
1349
|
-
workflowName?: string;
|
|
1350
|
-
status?: string;
|
|
1351
|
-
queueName?: number;
|
|
1352
|
-
startTime?: string;
|
|
1353
|
-
endTime?: string;
|
|
1354
|
-
limit?: number;
|
|
1355
|
-
offset?: number;
|
|
1356
|
-
sortDesc?: boolean;
|
|
1357
|
-
}
|
|
1358
|
-
```
|
|
1359
|
-
|
|
1360
|
-
Retrieve a list of WorkflowStatus of all **currently enqueued** workflows matching specified criteria.
|
|
1361
|
-
|
|
1362
|
-
**Parameters:**
|
|
1363
|
-
|
|
1364
|
-
- **workflowName**: Retrieve workflows with this name.
|
|
1365
|
-
- **status**: Retrieve workflows with this status (Must be `ENQUEUED`, `PENDING`, `SUCCESS`, `ERROR`, `CANCELLED`, or `RETRIES_EXCEEDED`)
|
|
1366
|
-
- **queueName**: Retrieve workflows running on this queue.
|
|
1367
|
-
- **startTime**: Retrieve workflows started after this (RFC 3339-compliant) timestamp.
|
|
1368
|
-
- **endTime**: Retrieve workflows started before this (RFC 3339-compliant) timestamp.
|
|
1369
|
-
- **limit**: Retrieve up to this many workflows.
|
|
1370
|
-
- **offset**: Skip this many workflows from the results returned (for pagination).
|
|
1371
|
-
- **sortDesc**: Whether to sort the results in descending (`True`) or ascending (`False`) order by workflow start time.
|
|
1372
|
-
|
|
1373
|
-
### SolidActions.listWorkflowSteps
|
|
1374
|
-
|
|
1375
|
-
```typescript
|
|
1376
|
-
SolidActions.listWorkflowSteps(
|
|
1377
|
-
workflowID: string)
|
|
1378
|
-
: Promise<StepInfo[]>
|
|
1379
|
-
```
|
|
1380
|
-
|
|
1381
|
-
Retrieve the steps of a workflow.
|
|
1382
|
-
This is a list of `StepInfo` objects, with the following structure:
|
|
1383
|
-
|
|
1384
|
-
```typescript
|
|
1385
|
-
interface StepInfo {
|
|
1386
|
-
// The unique ID of the step in the workflow. Zero-indexed.
|
|
1387
|
-
readonly functionID: number;
|
|
1388
|
-
// The name of the step
|
|
1389
|
-
readonly name: string;
|
|
1390
|
-
// The step's output, if any
|
|
1391
|
-
readonly output: unknown;
|
|
1392
|
-
// The error the step threw, if any
|
|
1393
|
-
readonly error: Error | null;
|
|
1394
|
-
// If the step starts or retrieves the result of a workflow, its ID
|
|
1395
|
-
readonly childWorkflowID: string | null;
|
|
1396
|
-
}
|
|
1397
|
-
```
|
|
1398
|
-
|
|
1399
|
-
### SolidActions.cancelWorkflow
|
|
1400
|
-
|
|
1401
|
-
```typescript
|
|
1402
|
-
cancelWorkflow(
|
|
1403
|
-
workflowID: string
|
|
1404
|
-
): Promise<void>
|
|
1405
|
-
```
|
|
1406
|
-
|
|
1407
|
-
Cancel a workflow.
|
|
1408
|
-
This sets is status to `CANCELLED`, removes it from its queue (if it is enqueued) and preempts its execution (interrupting it at the beginning of its next step)
|
|
1409
|
-
|
|
1410
|
-
### SolidActions.resumeWorkflow
|
|
1411
|
-
|
|
1412
|
-
```typescript
|
|
1413
|
-
SolidActions.resumeWorkflow<T>(
|
|
1414
|
-
workflowID: string
|
|
1415
|
-
): Promise<WorkflowHandle<Awaited<T>>>
|
|
1416
|
-
```
|
|
1417
|
-
|
|
1418
|
-
Resume a workflow.
|
|
1419
|
-
This immediately starts it from its last completed step.
|
|
1420
|
-
You can use this to resume workflows that are cancelled or have exceeded their maximum recovery attempts.
|
|
1421
|
-
You can also use this to start an enqueued workflow immediately, bypassing its queue.
|
|
1422
|
-
|
|
1423
|
-
### SolidActions.forkWorkflow
|
|
1424
|
-
|
|
1425
|
-
```typescript
|
|
1426
|
-
static async forkWorkflow<T>(
|
|
1427
|
-
workflowID: string,
|
|
1428
|
-
startStep: number,
|
|
1429
|
-
options?: { newWorkflowID?: string; applicationVersion?: string; timeoutMS?: number },
|
|
1430
|
-
): Promise<WorkflowHandle<Awaited<T>>>
|
|
1431
|
-
```
|
|
1432
|
-
|
|
1433
|
-
Start a new execution of a workflow from a specific step.
|
|
1434
|
-
The input step ID (`startStep`) must match the `functionID` of the step returned by `listWorkflowSteps`.
|
|
1435
|
-
The specified `startStep` is the step from which the new workflow will start, so any steps whose ID is less than `startStep` will not be re-executed.
|
|
1436
|
-
|
|
1437
|
-
**Parameters:**
|
|
1438
|
-
|
|
1439
|
-
- **workflowID**: The ID of the workflow to fork.
|
|
1440
|
-
- **startStep**: The ID of the step from which to start the forked workflow. Must match the `functionID` of the step in the original workflow execution.
|
|
1441
|
-
- **newWorkflowID**: The ID of the new workflow created by the fork. If not specified, a random UUID is used.
|
|
1442
|
-
- **applicationVersion**: The application version on which the forked workflow will run. Useful for "patching" workflows that failed due to a bug in the previous application version.
|
|
1443
|
-
- **timeoutMS**: A timeout for the forked workflow in milliseconds.
|
|
1444
|
-
|
|
1445
|
-
## Configuring SolidActions
|
|
1446
|
-
|
|
1447
|
-
To configure SolidActions, pass in a configuration with `SolidActions.setConfig` before you call `SolidActions.launch`.
|
|
1448
|
-
For example:
|
|
1449
|
-
|
|
1450
|
-
```javascript
|
|
1451
|
-
SolidActions.setConfig({
|
|
1452
|
-
name: 'my-app',
|
|
1453
|
-
systemDatabaseUrl: process.env.SolidActions_SYSTEM_DATABASE_URL,
|
|
1454
|
-
});
|
|
1455
|
-
await SolidActions.launch();
|
|
1456
|
-
```
|
|
1457
|
-
|
|
1458
|
-
A configuration object has the following fields.
|
|
1459
|
-
All fields except `name` are optional.
|
|
1460
|
-
|
|
1461
|
-
```javascript
|
|
1462
|
-
export interface SolidActionsConfig {
|
|
1463
|
-
name?: string;
|
|
1464
|
-
|
|
1465
|
-
systemDatabaseUrl?: string;
|
|
1466
|
-
systemDatabasePoolSize?: number;
|
|
1467
|
-
systemDatabasePool?: Pool;
|
|
1468
|
-
|
|
1469
|
-
enableOTLP?: boolean;
|
|
1470
|
-
logLevel?: string;
|
|
1471
|
-
otlpLogsEndpoints?: string[];
|
|
1472
|
-
otlpTracesEndpoints?: string[];
|
|
1473
|
-
|
|
1474
|
-
runAdminServer?: boolean;
|
|
1475
|
-
adminPort?: number;
|
|
1476
|
-
|
|
1477
|
-
applicationVersion?: string;
|
|
1478
|
-
}
|
|
1479
|
-
```
|
|
1480
|
-
|
|
1481
|
-
- **name**: Your application's name.
|
|
1482
|
-
- **systemDatabaseUrl**: A connection string to a Postgres database in which SolidActions can store internal state. The supported format is:
|
|
1483
|
-
|
|
1484
|
-
```
|
|
1485
|
-
postgresql://[username]:[password]@[hostname]:[port]/[database name]
|
|
1486
|
-
```
|
|
1487
|
-
|
|
1488
|
-
The default is:
|
|
1489
|
-
|
|
1490
|
-
```
|
|
1491
|
-
postgresql://postgres:dbos@localhost:5432/[application name]_dbos_sys
|
|
1492
|
-
```
|
|
1493
|
-
|
|
1494
|
-
If the Postgres database referenced by this connection string does not exist, SolidActions will attempt to create it.
|
|
1495
|
-
|
|
1496
|
-
- **systemDatabasePoolSize**: The size of the connection pool used for the SolidActions system database. Defaults to 10.
|
|
1497
|
-
- **systemDatabasePool**: A custom `node-postgres` connection pool to use to connect to your system database. If provided, SolidActions will not create a connection pool but use this instead.
|
|
1498
|
-
- **enableOTLP**: Enable SolidActions OpenTelemetry tracing and export. Defaults to False.
|
|
1499
|
-
- **logLevel**: Configure the SolidActions logger severity. Defaults to `info`.
|
|
1500
|
-
- **otlpTracesEndpoints**: SolidActions operations automatically generate OpenTelemetry Traces. Use this field to declare a list of OTLP-compatible receivers.
|
|
1501
|
-
- **otlpLogsEndpoints**: SolidActions operations automatically generate OpenTelemetry Logs. Use this field to declare a list of OTLP-compatible receivers.
|
|
1502
|
-
- **runAdminServer**: Whether to run an HTTP admin server for workflow management operations. Defaults to True.
|
|
1503
|
-
- **adminPort**: The port on which the admin server runs. Defaults to 3001.
|
|
1504
|
-
- **applicationVersion**: The code version for this application and its workflows.
|