@solidactions/sdk 0.1.1 → 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 +25 -4
- package/dist/src/cli/cli.js +0 -0
- 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/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,534 +0,0 @@
|
|
|
1
|
-
# Build Reliable Workflows With SolidSteps SDK
|
|
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 SolidSteps DBOS SDK. 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
|
-
- The SolidSteps SDK is a fork of `@dbos-inc/dbos-sdk` optimized for container-based execution
|
|
12
|
-
|
|
13
|
-
## SolidSteps vs Original DBOS
|
|
14
|
-
|
|
15
|
-
The SolidSteps SDK differs from the original `@dbos-inc/dbos-sdk` in key ways:
|
|
16
|
-
|
|
17
|
-
| Feature | Original DBOS | SolidSteps SDK |
|
|
18
|
-
| --------------- | ------------------------ | --------------------------------------------- |
|
|
19
|
-
| **Backend** | Direct PostgreSQL | HTTP API (Laravel backend) |
|
|
20
|
-
| **Sleep** | In-process wait | Container exits, scheduler wakes |
|
|
21
|
-
| **Recv** | In-process polling | Container exits, wakes on signal |
|
|
22
|
-
| **Config** | `setConfig()` required | Auto-config from `solidsteps.yaml` + env vars |
|
|
23
|
-
| **Entry Point** | Manual `main()` function | `DBOS.run()` one-liner |
|
|
24
|
-
|
|
25
|
-
### Key Behavioral Differences
|
|
26
|
-
|
|
27
|
-
**`DBOS.sleep(ms)`** - Container exits during sleep, zero resources consumed. Scheduler wakes workflow after duration. Supports sleeps of seconds to weeks.
|
|
28
|
-
|
|
29
|
-
**`DBOS.recv(topic?, timeoutSeconds?)`** - Container exits while waiting for message. Wakes immediately when signal arrives via HTTP endpoint. No polling, no resource usage during wait.
|
|
30
|
-
|
|
31
|
-
**`DBOS.send(destinationID, message, topic?)`** - Stores message AND wakes waiting destination workflow if it's in 'waiting' status.
|
|
32
|
-
|
|
33
|
-
## Simplified APIs (SolidSteps-Only)
|
|
34
|
-
|
|
35
|
-
### `DBOS.run(workflow)`
|
|
36
|
-
|
|
37
|
-
One-liner entry point that handles the full lifecycle:
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
41
|
-
|
|
42
|
-
async function myWorkflow(input: MyInput): Promise<MyOutput> {
|
|
43
|
-
// workflow logic
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const wf = DBOS.registerWorkflow(myWorkflow, { name: 'my-workflow' });
|
|
47
|
-
DBOS.run(wf);
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
`DBOS.run()` automatically:
|
|
51
|
-
|
|
52
|
-
1. Reads config from `solidsteps.yaml` + env vars (no `setConfig()` needed)
|
|
53
|
-
2. Calls `launch()`
|
|
54
|
-
3. Parses `WORKFLOW_INPUT` env var via `getInput()`
|
|
55
|
-
4. Runs the workflow and awaits result
|
|
56
|
-
5. Calls `shutdown()` and exits with code 0 (or 1 on error)
|
|
57
|
-
|
|
58
|
-
### `DBOS.getInput<T>()`
|
|
59
|
-
|
|
60
|
-
Parse the `WORKFLOW_INPUT` environment variable (set by runner from webhook payload):
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
interface MyInput {
|
|
64
|
-
taskId: string;
|
|
65
|
-
value: number;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Inside workflow function
|
|
69
|
-
const input = DBOS.getInput<MyInput>();
|
|
70
|
-
// Returns {} if WORKFLOW_INPUT not set or invalid JSON
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### `DBOS.getSignalUrls(topic?)`
|
|
74
|
-
|
|
75
|
-
Generate signal URLs for external signals (email buttons, webhooks):
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
// Inside a workflow
|
|
79
|
-
const urls = DBOS.getSignalUrls('approval');
|
|
80
|
-
// Returns:
|
|
81
|
-
// {
|
|
82
|
-
// base: "http://localhost:8000/api/signal/{workflowId}",
|
|
83
|
-
// approve: "http://localhost:8000/api/signal/{workflowId}?choice=approve&topic=approval",
|
|
84
|
-
// reject: "http://localhost:8000/api/signal/{workflowId}?choice=reject&topic=approval",
|
|
85
|
-
// custom: (action) => "http://localhost:8000/api/signal/{workflowId}?choice={action}&topic=approval"
|
|
86
|
-
// }
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Environment Variables
|
|
90
|
-
|
|
91
|
-
These environment variables are available inside workflow containers:
|
|
92
|
-
|
|
93
|
-
| Variable | Description |
|
|
94
|
-
| ---------------------- | --------------------------------------------------------------- |
|
|
95
|
-
| `WORKFLOW_INPUT` | JSON-encoded input from webhook payload |
|
|
96
|
-
| `DBOS_WORKFLOW_UUID` | Pre-generated workflow UUID (use this, don't generate your own) |
|
|
97
|
-
| `DBOS_API_URL` | SolidSteps API URL (e.g., `http://localhost:8000/api/internal`) |
|
|
98
|
-
| `DBOS_API_KEY` | Authentication token for API calls |
|
|
99
|
-
| `SOLIDACTIONS_API_URL` | Same as DBOS_API_URL (alternative name) |
|
|
100
|
-
| `APP_URL` | Base application URL for signal generation |
|
|
101
|
-
|
|
102
|
-
## Workflow Guidelines
|
|
103
|
-
|
|
104
|
-
Workflows provide durable execution so you can write programs that are resilient to any failure.
|
|
105
|
-
Workflows are comprised of steps, which are ordinary TypeScript functions called with `DBOS.runStep()`.
|
|
106
|
-
When using DBOS workflows, you should call any function that performs complex operations or accesses external APIs or services as a step using `DBOS.runStep()`.
|
|
107
|
-
|
|
108
|
-
If a workflow is interrupted for any reason (e.g., container exits during sleep), when the workflow resumes it automatically continues from the last completed step.
|
|
109
|
-
|
|
110
|
-
### Rules for Workflows
|
|
111
|
-
|
|
112
|
-
- If asked to add DBOS 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.
|
|
113
|
-
- When making a function a workflow, you should make all functions it calls steps. Do NOT change the functions in any way.
|
|
114
|
-
- Do NOT make functions steps unless they are DIRECTLY called by a workflow.
|
|
115
|
-
- 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: accessing an external API, reading files, generating a random number, getting the current time.
|
|
116
|
-
- 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.
|
|
117
|
-
- DBOS workflows and steps should NOT have side effects in memory outside of their own scope.
|
|
118
|
-
- Do NOT call any DBOS context method (`DBOS.send`, `DBOS.recv`, `DBOS.startWorkflow`, `DBOS.sleep`, `DBOS.setEvent`, `DBOS.getEvent`) from a step.
|
|
119
|
-
- Do NOT start workflows from inside a step.
|
|
120
|
-
|
|
121
|
-
## Complete Workflow Examples
|
|
122
|
-
|
|
123
|
-
### Simple Steps (Basic Pattern)
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
127
|
-
|
|
128
|
-
interface TaskInput {
|
|
129
|
-
taskId: string;
|
|
130
|
-
value: number;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
interface TaskResult {
|
|
134
|
-
taskId: string;
|
|
135
|
-
processedValue: number;
|
|
136
|
-
steps: string[];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async function initialize(taskId: string) {
|
|
140
|
-
console.log(`Initializing task: ${taskId}`);
|
|
141
|
-
return { initialized: true };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function validate(value: number) {
|
|
145
|
-
if (value < 0) throw new Error('Value must be non-negative');
|
|
146
|
-
return { valid: true, value };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function process(value: number) {
|
|
150
|
-
return { result: value * 2 + 10 };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async function simpleWorkflow(input: TaskInput): Promise<TaskResult> {
|
|
154
|
-
const taskId = input.taskId || 'default';
|
|
155
|
-
const value = input.value ?? 0;
|
|
156
|
-
const steps: string[] = [];
|
|
157
|
-
|
|
158
|
-
await DBOS.runStep(() => initialize(taskId), { name: 'initialize' });
|
|
159
|
-
steps.push('initialize');
|
|
160
|
-
|
|
161
|
-
const validation = await DBOS.runStep(() => validate(value), { name: 'validate' });
|
|
162
|
-
steps.push('validate');
|
|
163
|
-
|
|
164
|
-
const processed = await DBOS.runStep(() => process(validation.value), { name: 'process' });
|
|
165
|
-
steps.push('process');
|
|
166
|
-
|
|
167
|
-
return { taskId, processedValue: processed.result, steps };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const workflow = DBOS.registerWorkflow(simpleWorkflow, { name: 'simple-workflow' });
|
|
171
|
-
DBOS.run(workflow);
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### Durable Sleep
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
178
|
-
|
|
179
|
-
interface SleepInput {
|
|
180
|
-
taskId: string;
|
|
181
|
-
sleepMs?: number;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async function recordStart() {
|
|
185
|
-
return { startedAt: new Date().toISOString(), startMs: Date.now() };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async function recordEnd(startMs: number) {
|
|
189
|
-
const endMs = Date.now();
|
|
190
|
-
return {
|
|
191
|
-
completedAt: new Date().toISOString(),
|
|
192
|
-
durationMs: endMs - startMs,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async function sleepWorkflow(input: SleepInput) {
|
|
197
|
-
const sleepMs = input.sleepMs ?? 5000;
|
|
198
|
-
|
|
199
|
-
const start = await DBOS.runStep(() => recordStart(), { name: 'record-start' });
|
|
200
|
-
|
|
201
|
-
console.log(`Sleeping for ${sleepMs}ms...`);
|
|
202
|
-
// Container exits here, scheduler wakes after duration
|
|
203
|
-
await DBOS.sleep(sleepMs);
|
|
204
|
-
console.log('Woke up!');
|
|
205
|
-
|
|
206
|
-
const end = await DBOS.runStep(() => recordEnd(start.startMs), { name: 'record-end' });
|
|
207
|
-
|
|
208
|
-
return {
|
|
209
|
-
taskId: input.taskId,
|
|
210
|
-
sleepMs,
|
|
211
|
-
actualDurationMs: end.durationMs,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const workflow = DBOS.registerWorkflow(sleepWorkflow, { name: 'sleep-workflow' });
|
|
216
|
-
DBOS.run(workflow);
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### Human-in-the-Loop (External Signals)
|
|
220
|
-
|
|
221
|
-
```typescript
|
|
222
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
223
|
-
|
|
224
|
-
interface ApprovalInput {
|
|
225
|
-
invoiceId: string;
|
|
226
|
-
amount: number;
|
|
227
|
-
approverEmail: string;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function createInvoice(input: ApprovalInput) {
|
|
231
|
-
console.log(`Created invoice ${input.invoiceId} for $${input.amount}`);
|
|
232
|
-
return { invoiceId: input.invoiceId, createdAt: new Date().toISOString() };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
async function sendEmail(email: string, urls: { approve: string; reject: string }) {
|
|
236
|
-
console.log(`Sending approval email to ${email}`);
|
|
237
|
-
console.log(` Approve: ${urls.approve}`);
|
|
238
|
-
console.log(` Reject: ${urls.reject}`);
|
|
239
|
-
return { sent: true };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function markApproved(invoiceId: string) {
|
|
243
|
-
console.log(`Invoice ${invoiceId} APPROVED`);
|
|
244
|
-
return { status: 'approved' as const };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async function markRejected(invoiceId: string, reason?: string) {
|
|
248
|
-
console.log(`Invoice ${invoiceId} REJECTED: ${reason || 'No reason given'}`);
|
|
249
|
-
return { status: 'rejected' as const, reason };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async function approvalWorkflow(input: ApprovalInput) {
|
|
253
|
-
const invoiceId = input.invoiceId || 'INV-001';
|
|
254
|
-
|
|
255
|
-
// Step 1: Create invoice
|
|
256
|
-
await DBOS.runStep(() => createInvoice(input), { name: 'create-invoice' });
|
|
257
|
-
|
|
258
|
-
// Step 2: Generate signal URLs and send email
|
|
259
|
-
const urls = DBOS.getSignalUrls('approval');
|
|
260
|
-
await DBOS.runStep(() => sendEmail(input.approverEmail, urls), { name: 'send-email' });
|
|
261
|
-
|
|
262
|
-
// Step 3: Wait for human response
|
|
263
|
-
// Container exits here, wakes when signal arrives via POST /api/signal/{workflowId}
|
|
264
|
-
console.log('Waiting for approval...');
|
|
265
|
-
const response = await DBOS.recv<{ choice: string; reason?: string }>('approval');
|
|
266
|
-
|
|
267
|
-
// Step 4: Process response
|
|
268
|
-
if (!response) {
|
|
269
|
-
return { invoiceId, status: 'timeout' as const };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (response.choice === 'approve') {
|
|
273
|
-
await DBOS.runStep(() => markApproved(invoiceId), { name: 'mark-approved' });
|
|
274
|
-
return { invoiceId, status: 'approved' as const };
|
|
275
|
-
} else {
|
|
276
|
-
await DBOS.runStep(() => markRejected(invoiceId, response.reason), { name: 'mark-rejected' });
|
|
277
|
-
return { invoiceId, status: 'rejected' as const, reason: response.reason };
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const workflow = DBOS.registerWorkflow(approvalWorkflow, { name: 'approval-workflow' });
|
|
282
|
-
DBOS.run(workflow);
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### Child Workflows
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
289
|
-
|
|
290
|
-
interface ChildInput {
|
|
291
|
-
parentId: string;
|
|
292
|
-
value: number;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async function processChild(value: number) {
|
|
296
|
-
return { result: value * 2 };
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async function childWorkflow(input: ChildInput) {
|
|
300
|
-
const processed = await DBOS.runStep(() => processChild(input.value), { name: 'process' });
|
|
301
|
-
return {
|
|
302
|
-
parentId: input.parentId,
|
|
303
|
-
inputValue: input.value,
|
|
304
|
-
outputValue: processed.result,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const childTask = DBOS.registerWorkflow(childWorkflow, { name: 'child-task' });
|
|
309
|
-
|
|
310
|
-
async function parentWorkflow(input: { parentId: string; value: number }) {
|
|
311
|
-
console.log('Spawning child workflow...');
|
|
312
|
-
|
|
313
|
-
// Start child and wait for result
|
|
314
|
-
const childHandle = await DBOS.startWorkflow(childTask)({
|
|
315
|
-
parentId: input.parentId,
|
|
316
|
-
value: input.value,
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
const childResult = await childHandle.getResult();
|
|
320
|
-
console.log(`Child completed: ${childResult.outputValue}`);
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
parentId: input.parentId,
|
|
324
|
-
childResult,
|
|
325
|
-
finalValue: childResult.outputValue + 100,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const parent = DBOS.registerWorkflow(parentWorkflow, { name: 'parent-workflow' });
|
|
330
|
-
DBOS.run(parent);
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
### Retry with Backoff
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
337
|
-
|
|
338
|
-
async function unreliableApiCall(url: string) {
|
|
339
|
-
// This might fail randomly
|
|
340
|
-
const response = await fetch(url);
|
|
341
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
342
|
-
return response.json();
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
async function retryWorkflow(input: { url: string }) {
|
|
346
|
-
// Retry up to 5 times with exponential backoff: 1s, 2s, 4s, 8s
|
|
347
|
-
const result = await DBOS.runStep(() => unreliableApiCall(input.url), {
|
|
348
|
-
name: 'api-call',
|
|
349
|
-
retriesAllowed: true,
|
|
350
|
-
maxAttempts: 5,
|
|
351
|
-
intervalSeconds: 1,
|
|
352
|
-
backoffRate: 2,
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
return { success: true, data: result };
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const workflow = DBOS.registerWorkflow(retryWorkflow, { name: 'retry-workflow' });
|
|
359
|
-
DBOS.run(workflow);
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
## Step Configuration Options
|
|
363
|
-
|
|
364
|
-
```typescript
|
|
365
|
-
interface StepConfig {
|
|
366
|
-
name?: string; // Step name for tracking/debugging
|
|
367
|
-
retriesAllowed?: boolean; // Enable retries (default: false)
|
|
368
|
-
intervalSeconds?: number; // Initial retry delay (default: 1)
|
|
369
|
-
maxAttempts?: number; // Max retry attempts (default: 3)
|
|
370
|
-
backoffRate?: number; // Retry delay multiplier (default: 2)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Example with full config
|
|
374
|
-
await DBOS.runStep(() => myStepFunction(args), {
|
|
375
|
-
name: 'my-step',
|
|
376
|
-
retriesAllowed: true,
|
|
377
|
-
maxAttempts: 10,
|
|
378
|
-
intervalSeconds: 0.5,
|
|
379
|
-
backoffRate: 2,
|
|
380
|
-
});
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
## Messaging Between Workflows
|
|
384
|
-
|
|
385
|
-
### Send a Message
|
|
386
|
-
|
|
387
|
-
```typescript
|
|
388
|
-
// From workflow or external code
|
|
389
|
-
await DBOS.send(destinationWorkflowID, { data: 'hello' }, 'my-topic');
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Receive a Message
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
// Inside a workflow - container exits if no message, wakes when one arrives
|
|
396
|
-
const message = await DBOS.recv<{ data: string }>('my-topic', 60); // 60 second timeout
|
|
397
|
-
if (message) {
|
|
398
|
-
console.log('Received:', message.data);
|
|
399
|
-
} else {
|
|
400
|
-
console.log('Timeout - no message received');
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
## Sending External Signals
|
|
405
|
-
|
|
406
|
-
From outside the workflow (e.g., a button click, webhook, or curl command):
|
|
407
|
-
|
|
408
|
-
```bash
|
|
409
|
-
# Approve an invoice workflow
|
|
410
|
-
curl -X POST "http://localhost:8000/api/signal/{workflowId}?choice=approve&topic=approval"
|
|
411
|
-
|
|
412
|
-
# With custom message body
|
|
413
|
-
curl -X POST "http://localhost:8000/api/signal/{workflowId}" \
|
|
414
|
-
-H "Content-Type: application/json" \
|
|
415
|
-
-d '{"topic": "approval", "message": {"choice": "reject", "reason": "Budget exceeded"}}'
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
## Project Structure
|
|
419
|
-
|
|
420
|
-
Every SolidSteps workflow project needs a `solidsteps.yaml`:
|
|
421
|
-
|
|
422
|
-
```yaml
|
|
423
|
-
project: my-workflow-project
|
|
424
|
-
entrypoint: src/main.ts
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
The SDK reads the project name from this file automatically.
|
|
428
|
-
|
|
429
|
-
## Common Mistakes to Avoid
|
|
430
|
-
|
|
431
|
-
### DON'T: Call DBOS methods from inside a step
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
// WRONG - don't call DBOS.sleep() inside a step
|
|
435
|
-
async function myStep() {
|
|
436
|
-
await DBOS.sleep(1000); // BAD!
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// CORRECT - call DBOS.sleep() directly in the workflow
|
|
440
|
-
async function myWorkflow() {
|
|
441
|
-
await DBOS.runStep(() => doWork(), { name: 'work' });
|
|
442
|
-
await DBOS.sleep(1000); // GOOD
|
|
443
|
-
}
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
### DON'T: Use non-deterministic operations directly in workflow
|
|
447
|
-
|
|
448
|
-
```typescript
|
|
449
|
-
// WRONG - non-deterministic in workflow
|
|
450
|
-
async function myWorkflow() {
|
|
451
|
-
const randomId = Math.random(); // BAD - different on replay
|
|
452
|
-
const now = new Date(); // BAD - different on replay
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// CORRECT - wrap in a step
|
|
456
|
-
async function generateId() {
|
|
457
|
-
return Math.random();
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
async function myWorkflow() {
|
|
461
|
-
const randomId = await DBOS.runStep(() => generateId(), { name: 'gen-id' });
|
|
462
|
-
}
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
### DON'T: Start workflows from inside a step
|
|
466
|
-
|
|
467
|
-
```typescript
|
|
468
|
-
// WRONG
|
|
469
|
-
async function myStep() {
|
|
470
|
-
await DBOS.startWorkflow(otherWorkflow)(input); // BAD!
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// CORRECT - start from workflow
|
|
474
|
-
async function myWorkflow() {
|
|
475
|
-
await DBOS.runStep(() => doPrep(), { name: 'prep' });
|
|
476
|
-
const handle = await DBOS.startWorkflow(otherWorkflow)(input); // GOOD
|
|
477
|
-
await handle.getResult();
|
|
478
|
-
}
|
|
479
|
-
```
|
|
480
|
-
|
|
481
|
-
### DON'T: Forget to handle defaults for input
|
|
482
|
-
|
|
483
|
-
```typescript
|
|
484
|
-
// WRONG - will crash if input is empty
|
|
485
|
-
async function myWorkflow(input: { taskId: string }) {
|
|
486
|
-
console.log(input.taskId); // undefined if webhook has no body
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// CORRECT - apply defaults
|
|
490
|
-
async function myWorkflow(input: { taskId?: string }) {
|
|
491
|
-
const taskId = input.taskId || 'default-task';
|
|
492
|
-
}
|
|
493
|
-
```
|
|
494
|
-
|
|
495
|
-
## Logging
|
|
496
|
-
|
|
497
|
-
Always log errors like this:
|
|
498
|
-
|
|
499
|
-
```typescript
|
|
500
|
-
DBOS.logger.error(`Error: ${(error as Error).message}`);
|
|
501
|
-
DBOS.logger.info('Processing started');
|
|
502
|
-
DBOS.logger.debug('Debug details...');
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
## Testing DBOS Functions
|
|
506
|
-
|
|
507
|
-
For testing workflows:
|
|
508
|
-
|
|
509
|
-
```typescript
|
|
510
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
511
|
-
|
|
512
|
-
beforeAll(async () => {
|
|
513
|
-
DBOS.setConfig({
|
|
514
|
-
name: 'test-app',
|
|
515
|
-
databaseUrl: process.env.DBOS_TESTING_DATABASE_URL,
|
|
516
|
-
});
|
|
517
|
-
await DBOS.launch();
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
afterAll(async () => {
|
|
521
|
-
await DBOS.shutdown();
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
test('workflow completes successfully', async () => {
|
|
525
|
-
const handle = await DBOS.startWorkflow(myWorkflow)({ taskId: 'test' });
|
|
526
|
-
const result = await handle.getResult();
|
|
527
|
-
expect(result.status).toBe('success');
|
|
528
|
-
});
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
---
|
|
532
|
-
|
|
533
|
-
_SolidSteps SDK - Built on DBOS for container-based durable execution_
|
|
534
|
-
_Last updated: 2026-01-12_
|