@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
|
@@ -0,0 +1,1207 @@
|
|
|
1
|
+
# SolidActions SDK Reference for AI Coding Assistants
|
|
2
|
+
|
|
3
|
+
This document is the comprehensive reference for the **SolidActions SDK** (`@solidactions/sdk`). It covers the full TypeScript API for building durable, checkpointed workflows.
|
|
4
|
+
|
|
5
|
+
**For platform deployment, webhook configuration, solidactions.yaml, CLI usage, and environment variables**, see the [platform reference](https://github.com/SolidActions/solidactions) (`docs/platform-reference.md`) in the SolidActions platform repo.
|
|
6
|
+
|
|
7
|
+
## Single Import Rule
|
|
8
|
+
|
|
9
|
+
All SDK imports come from one package:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Additional named exports (when needed):
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { SolidActions, SolidActionsClient, ConfiguredInstance } from '@solidactions/sdk';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
A minimal workflow with two sequential steps:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
29
|
+
|
|
30
|
+
async function greet(name: string): Promise<string> {
|
|
31
|
+
return `Hello, ${name}!`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function greetingWorkflow(name: string): Promise<string> {
|
|
35
|
+
const greeting = await SolidActions.runStep(() => greet(name), { name: 'greet' });
|
|
36
|
+
SolidActions.logger.info(greeting);
|
|
37
|
+
return greeting;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const workflow = SolidActions.registerWorkflow(greetingWorkflow);
|
|
41
|
+
|
|
42
|
+
// Platform entry point (reads input from WORKFLOW_INPUT env var):
|
|
43
|
+
SolidActions.run(greetingWorkflow);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Lifecycle
|
|
49
|
+
|
|
50
|
+
### `SolidActions.run()`
|
|
51
|
+
|
|
52
|
+
The primary entry point for platform-deployed workflows. Handles `launch()`, `startWorkflow()`, `getResult()`, and `shutdown()` automatically.
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
static async run<T, R>(
|
|
56
|
+
workflow: (input: T) => Promise<R>,
|
|
57
|
+
options?: {
|
|
58
|
+
input?: T; // Pre-parsed input (overrides WORKFLOW_INPUT)
|
|
59
|
+
workflowID?: string; // Custom workflow ID
|
|
60
|
+
}
|
|
61
|
+
): Promise<void>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
When deployed on the SolidActions platform, `run()` reads the workflow input from the `WORKFLOW_INPUT` environment variable, executes the workflow, and exits the process.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
68
|
+
|
|
69
|
+
async function processOrder(order: { id: string; items: string[] }): Promise<void> {
|
|
70
|
+
await SolidActions.runStep(() => validateOrder(order), { name: 'validate' });
|
|
71
|
+
await SolidActions.runStep(() => chargePayment(order.id), { name: 'charge' });
|
|
72
|
+
await SolidActions.runStep(() => shipOrder(order.id), { name: 'ship' });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
SolidActions.run(processOrder);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `SolidActions.getInput()`
|
|
79
|
+
|
|
80
|
+
Reads and parses the `WORKFLOW_INPUT` environment variable. Returns `{}` if the variable is missing or unparseable.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
static getInput<T = Record<string, unknown>>(): T
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
88
|
+
|
|
89
|
+
interface MyInput {
|
|
90
|
+
userId: string;
|
|
91
|
+
action: string;
|
|
92
|
+
}
|
|
93
|
+
const input = SolidActions.getInput<MyInput>();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `SolidActions.setConfig()` / `launch()` / `shutdown()`
|
|
97
|
+
|
|
98
|
+
Manual lifecycle for local development, testing, or standalone usage. Not needed when using `SolidActions.run()`.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
static setConfig(config: SolidActionsConfig): void
|
|
102
|
+
static async launch(options?: SolidActionsLaunchOptions): Promise<void>
|
|
103
|
+
static async shutdown(): Promise<void>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
108
|
+
|
|
109
|
+
async function main() {
|
|
110
|
+
SolidActions.setConfig({
|
|
111
|
+
name: 'my-app',
|
|
112
|
+
api: {
|
|
113
|
+
url: process.env.SOLIDACTIONS_API_URL!,
|
|
114
|
+
key: process.env.SOLIDACTIONS_API_KEY!,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
await SolidActions.launch();
|
|
118
|
+
|
|
119
|
+
const handle = await SolidActions.startWorkflow(myWorkflow)('arg1');
|
|
120
|
+
const result = await handle.getResult();
|
|
121
|
+
|
|
122
|
+
await SolidActions.shutdown();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
main().catch(console.error);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Workflows
|
|
131
|
+
|
|
132
|
+
### Registering Workflows
|
|
133
|
+
|
|
134
|
+
#### Functional API (preferred)
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
static registerWorkflow<This, Args extends unknown[], Return>(
|
|
138
|
+
func: (this: This, ...args: Args) => Promise<Return>,
|
|
139
|
+
config?: { name?: string } & WorkflowConfig
|
|
140
|
+
): (this: This, ...args: Args) => Promise<Return>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
145
|
+
|
|
146
|
+
async function processData(data: string): Promise<string> {
|
|
147
|
+
const cleaned = await SolidActions.runStep(() => cleanData(data), { name: 'clean' });
|
|
148
|
+
const result = await SolidActions.runStep(() => transform(cleaned), { name: 'transform' });
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const processDataWorkflow = SolidActions.registerWorkflow(processData);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Decorator API (class-based)
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
@SolidActions.workflow(config?: WorkflowConfig)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
163
|
+
|
|
164
|
+
class DataProcessor {
|
|
165
|
+
@SolidActions.workflow()
|
|
166
|
+
static async processData(data: string): Promise<string> {
|
|
167
|
+
const cleaned = await SolidActions.runStep(() => cleanData(data), { name: 'clean' });
|
|
168
|
+
return cleaned;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### WorkflowConfig
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
interface WorkflowConfig {
|
|
177
|
+
maxRecoveryAttempts?: number; // Default: 100
|
|
178
|
+
name?: string; // Override the workflow function name
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Determinism Rules
|
|
183
|
+
|
|
184
|
+
Workflow functions **must be deterministic**: given the same inputs, they must invoke the same steps in the same order. All non-deterministic operations must be inside steps:
|
|
185
|
+
|
|
186
|
+
**Do NOT do this in a workflow function:**
|
|
187
|
+
|
|
188
|
+
- HTTP requests (`fetch`, API calls)
|
|
189
|
+
- File system access
|
|
190
|
+
- Random number generation (use `SolidActions.randomUUID()` or wrap in a step)
|
|
191
|
+
- Get current time (use `SolidActions.now()` or wrap in a step)
|
|
192
|
+
- Access databases
|
|
193
|
+
|
|
194
|
+
**Safe inside workflow functions:**
|
|
195
|
+
|
|
196
|
+
- Loops, branches, conditionals (deterministic logic)
|
|
197
|
+
- Calling `SolidActions.runStep()`
|
|
198
|
+
- Calling `SolidActions.sleep()`, `SolidActions.send()`, `SolidActions.recv()`
|
|
199
|
+
- Calling `SolidActions.setEvent()`, `SolidActions.getEvent()`
|
|
200
|
+
- Calling `SolidActions.now()`, `SolidActions.randomUUID()`
|
|
201
|
+
- Calling `SolidActions.startWorkflow()`
|
|
202
|
+
|
|
203
|
+
### Workflow IDs and Idempotency
|
|
204
|
+
|
|
205
|
+
Every workflow execution gets a unique ID (UUID by default). You can assign a custom ID via `SolidActions.startWorkflow()`. A custom workflow ID acts as an **idempotency key**: calling a workflow with the same ID multiple times executes it only once.
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
209
|
+
|
|
210
|
+
async function chargeCustomer(customerId: string, amount: number): Promise<void> {
|
|
211
|
+
await SolidActions.runStep(() => processPayment(customerId, amount), { name: 'charge' });
|
|
212
|
+
}
|
|
213
|
+
const chargeWorkflow = SolidActions.registerWorkflow(chargeCustomer);
|
|
214
|
+
|
|
215
|
+
// Idempotent: even if called twice, charges only once
|
|
216
|
+
const handle = await SolidActions.startWorkflow(chargeWorkflow, {
|
|
217
|
+
workflowID: `charge-${orderId}`,
|
|
218
|
+
})(customerId, amount);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Workflow Timeouts
|
|
222
|
+
|
|
223
|
+
Set a timeout via `SolidActions.startWorkflow()`. When the timeout expires, the workflow and all its children are cancelled. Timeouts are **start-to-completion** and **durable** (persist across restarts).
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
227
|
+
|
|
228
|
+
const handle = await SolidActions.startWorkflow(myWorkflow, {
|
|
229
|
+
timeoutMS: 60000, // 60 seconds
|
|
230
|
+
})('input');
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Starting Workflows in the Background
|
|
234
|
+
|
|
235
|
+
Use `SolidActions.startWorkflow()` to start a workflow in the background and get a `WorkflowHandle`:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
static startWorkflow<Args extends unknown[], Return>(
|
|
239
|
+
target: (...args: Args) => Promise<Return>,
|
|
240
|
+
params?: StartWorkflowParams
|
|
241
|
+
): (...args: Args) => Promise<WorkflowHandle<Return>>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`StartWorkflowParams`:
|
|
245
|
+
|
|
246
|
+
- `workflowID?: string` — Custom workflow ID (acts as idempotency key)
|
|
247
|
+
- `timeoutMS?: number` — Timeout in milliseconds
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
251
|
+
|
|
252
|
+
async function parentWorkflow(): Promise<void> {
|
|
253
|
+
// Start child workflow in background
|
|
254
|
+
const handle = await SolidActions.startWorkflow(childWorkflow)('arg1');
|
|
255
|
+
|
|
256
|
+
// Do other work...
|
|
257
|
+
await SolidActions.runStep(() => doSomethingElse(), { name: 'other' });
|
|
258
|
+
|
|
259
|
+
// Wait for child result
|
|
260
|
+
const childResult = await handle.getResult();
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Retrieving a Workflow by ID
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
static retrieveWorkflow<T = unknown>(workflowID: string): WorkflowHandle<Awaited<T>>
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
272
|
+
|
|
273
|
+
const handle = SolidActions.retrieveWorkflow<string>('my-workflow-id');
|
|
274
|
+
const status = await handle.getStatus();
|
|
275
|
+
const result = await handle.getResult();
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Steps
|
|
281
|
+
|
|
282
|
+
Steps are the building blocks of workflows. They wrap ordinary functions and provide checkpointing — if a workflow is interrupted, it resumes from the last completed step.
|
|
283
|
+
|
|
284
|
+
### `SolidActions.runStep()` (preferred)
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
static runStep<Return>(
|
|
288
|
+
func: () => Promise<Return>,
|
|
289
|
+
config?: StepConfig & { name?: string }
|
|
290
|
+
): Promise<Return>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
295
|
+
|
|
296
|
+
async function myWorkflow(): Promise<void> {
|
|
297
|
+
const data = await SolidActions.runStep(() => fetchFromApi(), { name: 'fetchData' });
|
|
298
|
+
await SolidActions.runStep(() => saveToDb(data), { name: 'saveData' });
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### `SolidActions.registerStep()` (alternative)
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
static registerStep<This, Args extends unknown[], Return>(
|
|
306
|
+
func: (this: This, ...args: Args) => Promise<Return>,
|
|
307
|
+
config?: StepConfig & { name?: string }
|
|
308
|
+
): (this: This, ...args: Args) => Promise<Return>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
313
|
+
|
|
314
|
+
async function fetchData(url: string): Promise<string> {
|
|
315
|
+
return await fetch(url).then((r) => r.text());
|
|
316
|
+
}
|
|
317
|
+
const fetchStep = SolidActions.registerStep(fetchData, { name: 'fetchData' });
|
|
318
|
+
|
|
319
|
+
async function myWorkflow(url: string): Promise<string> {
|
|
320
|
+
return await fetchStep(url);
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### `@SolidActions.step()` Decorator (alternative)
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
328
|
+
|
|
329
|
+
class MyService {
|
|
330
|
+
@SolidActions.step()
|
|
331
|
+
static async fetchData(url: string): Promise<string> {
|
|
332
|
+
return await fetch(url).then((r) => r.text());
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@SolidActions.workflow()
|
|
336
|
+
static async processUrl(url: string): Promise<string> {
|
|
337
|
+
return await MyService.fetchData(url);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### StepConfig
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
interface StepConfig {
|
|
346
|
+
retriesAllowed?: boolean; // Enable automatic retries (default: false)
|
|
347
|
+
intervalSeconds?: number; // Seconds before first retry (default: 1)
|
|
348
|
+
maxAttempts?: number; // Maximum retry attempts (default: 3)
|
|
349
|
+
backoffRate?: number; // Retry interval multiplier (default: 2)
|
|
350
|
+
name?: string; // Step name for identification
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Example with retries:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
358
|
+
|
|
359
|
+
async function myWorkflow(): Promise<string> {
|
|
360
|
+
return await SolidActions.runStep(() => callUnreliableApi(), {
|
|
361
|
+
name: 'callApi',
|
|
362
|
+
retriesAllowed: true,
|
|
363
|
+
maxAttempts: 5,
|
|
364
|
+
intervalSeconds: 2,
|
|
365
|
+
backoffRate: 3,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Parallel Step Execution
|
|
371
|
+
|
|
372
|
+
Use `Promise.allSettled()` to run steps in parallel. Steps must be **started in a deterministic order** (the array literal order is deterministic):
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
376
|
+
|
|
377
|
+
async function parallelWorkflow(): Promise<void> {
|
|
378
|
+
// CORRECT: steps started in deterministic order
|
|
379
|
+
const results = await Promise.allSettled([
|
|
380
|
+
SolidActions.runStep(() => fetchUserProfile(userId), { name: 'profile' }),
|
|
381
|
+
SolidActions.runStep(() => fetchUserOrders(userId), { name: 'orders' }),
|
|
382
|
+
SolidActions.runStep(() => fetchUserPrefs(userId), { name: 'prefs' }),
|
|
383
|
+
]);
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Do NOT use `Promise.all()`** — when any promise rejects, `Promise.all` immediately fails, leaving other promises unresolved. If one of those later throws, it crashes the Node.js process. Always use `Promise.allSettled()`.
|
|
388
|
+
|
|
389
|
+
**Do NOT nest async functions in `Promise.allSettled()`** — the execution order of steps inside nested async functions is non-deterministic:
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
// WRONG: step2 and step4 may execute in either order
|
|
393
|
+
const results = await Promise.allSettled([
|
|
394
|
+
async () => {
|
|
395
|
+
await step1();
|
|
396
|
+
await step2();
|
|
397
|
+
},
|
|
398
|
+
async () => {
|
|
399
|
+
await step3();
|
|
400
|
+
await step4();
|
|
401
|
+
},
|
|
402
|
+
]);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
For sequences of operations in parallel, use child workflows via `SolidActions.startWorkflow()`.
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Durable Primitives
|
|
410
|
+
|
|
411
|
+
### `SolidActions.sleep()` / `sleepms()` / `sleepSeconds()`
|
|
412
|
+
|
|
413
|
+
Durable sleep that persists across restarts. The wakeup time is saved so the workflow always wakes on schedule.
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
static async sleep(durationMS: number): Promise<void>
|
|
417
|
+
static async sleepms(durationMS: number): Promise<void>
|
|
418
|
+
static async sleepSeconds(durationSec: number): Promise<void>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
423
|
+
|
|
424
|
+
async function reminderWorkflow(userId: string): Promise<void> {
|
|
425
|
+
await SolidActions.runStep(() => sendInitialEmail(userId), { name: 'sendEmail' });
|
|
426
|
+
await SolidActions.sleep(86400000); // Sleep 24 hours (durable)
|
|
427
|
+
await SolidActions.runStep(() => sendFollowUp(userId), { name: 'followUp' });
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### `SolidActions.now()`
|
|
432
|
+
|
|
433
|
+
Returns the current time as a UNIX epoch timestamp in milliseconds. Deterministic — on recovery, returns the same value that was recorded during the original execution.
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
static async now(): Promise<number>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
441
|
+
|
|
442
|
+
async function timedWorkflow(): Promise<void> {
|
|
443
|
+
const startTime = await SolidActions.now();
|
|
444
|
+
await SolidActions.runStep(() => doWork(), { name: 'work' });
|
|
445
|
+
const endTime = await SolidActions.now();
|
|
446
|
+
SolidActions.logger.info(`Elapsed: ${endTime - startTime}ms`);
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### `SolidActions.randomUUID()`
|
|
451
|
+
|
|
452
|
+
Generates a deterministic UUID. On recovery, returns the same UUID that was generated during the original execution.
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
static async randomUUID(): Promise<string>
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
460
|
+
|
|
461
|
+
async function createEntityWorkflow(name: string): Promise<string> {
|
|
462
|
+
const entityId = await SolidActions.randomUUID();
|
|
463
|
+
await SolidActions.runStep(() => saveEntity(entityId, name), { name: 'save' });
|
|
464
|
+
return entityId;
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Communication
|
|
471
|
+
|
|
472
|
+
### Messaging: `send()` / `recv()`
|
|
473
|
+
|
|
474
|
+
Send messages to a workflow by its ID. Messages are queued per topic.
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
static async send<T>(
|
|
478
|
+
destinationID: string,
|
|
479
|
+
message: T,
|
|
480
|
+
topic?: string,
|
|
481
|
+
idempotencyKey?: string
|
|
482
|
+
): Promise<void>
|
|
483
|
+
|
|
484
|
+
static async recv<T>(
|
|
485
|
+
topic?: string,
|
|
486
|
+
timeoutSeconds?: number
|
|
487
|
+
): Promise<T | null>
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
- `send()` can be called from anywhere (workflow, step, or outside via `SolidActionsClient`).
|
|
491
|
+
- `recv()` can only be called from a workflow function (not from steps).
|
|
492
|
+
- Messages without a topic are separate from messages with topics.
|
|
493
|
+
- `recv()` returns `null` if the timeout expires.
|
|
494
|
+
- All messages are persisted — if `send` completes, the receiver is guaranteed to get it.
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
498
|
+
|
|
499
|
+
// Approval workflow: waits for an external signal
|
|
500
|
+
async function approvalWorkflow(requestId: string): Promise<string> {
|
|
501
|
+
await SolidActions.runStep(() => sendApprovalRequest(requestId), { name: 'requestApproval' });
|
|
502
|
+
|
|
503
|
+
// Wait up to 24 hours for approval
|
|
504
|
+
const decision = await SolidActions.recv<string>('approval', 86400);
|
|
505
|
+
if (decision === 'approved') {
|
|
506
|
+
await SolidActions.runStep(() => executeRequest(requestId), { name: 'execute' });
|
|
507
|
+
return 'completed';
|
|
508
|
+
}
|
|
509
|
+
return 'rejected';
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// External caller sends approval:
|
|
513
|
+
await SolidActions.send(workflowID, 'approved', 'approval');
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Events: `setEvent()` / `getEvent()`
|
|
517
|
+
|
|
518
|
+
Publish key-value pairs from within a workflow. Useful for status updates or communicating intermediate results.
|
|
519
|
+
|
|
520
|
+
```typescript
|
|
521
|
+
static async setEvent<T>(key: string, value: T): Promise<void>
|
|
522
|
+
|
|
523
|
+
static async getEvent<T>(
|
|
524
|
+
workflowID: string,
|
|
525
|
+
key: string,
|
|
526
|
+
timeoutSeconds?: number
|
|
527
|
+
): Promise<T | null>
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
- `setEvent()` can only be called from a workflow function (not from steps).
|
|
531
|
+
- `getEvent()` can be called from anywhere.
|
|
532
|
+
- Events are persisted and the latest value is always retrievable.
|
|
533
|
+
- `getEvent()` waits for the event to be published, returning `null` on timeout.
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
537
|
+
|
|
538
|
+
async function checkoutWorkflow(orderId: string): Promise<void> {
|
|
539
|
+
const paymentUrl = await SolidActions.runStep(() => createPaymentSession(orderId), { name: 'createPayment' });
|
|
540
|
+
|
|
541
|
+
// Publish payment URL for the caller
|
|
542
|
+
await SolidActions.setEvent('paymentUrl', paymentUrl);
|
|
543
|
+
|
|
544
|
+
// Wait for payment confirmation
|
|
545
|
+
const confirmation = await SolidActions.recv<string>('paymentComplete', 3600);
|
|
546
|
+
if (confirmation) {
|
|
547
|
+
await SolidActions.runStep(() => fulfillOrder(orderId), { name: 'fulfill' });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Caller reads the payment URL:
|
|
552
|
+
const url = await SolidActions.getEvent<string>(handle.workflowID, 'paymentUrl', 30);
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Streaming: `writeStream()` / `readStream()` / `closeStream()`
|
|
556
|
+
|
|
557
|
+
Stream data in real time from workflows to clients. Useful for LLM streaming, progress reporting, or long-running result feeds.
|
|
558
|
+
|
|
559
|
+
```typescript
|
|
560
|
+
static async writeStream<T>(key: string, value: T): Promise<void>
|
|
561
|
+
static async closeStream(key: string): Promise<void>
|
|
562
|
+
static async *readStream<T>(workflowID: string, key: string): AsyncGenerator<T, void, unknown>
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
- `writeStream()` can be called from workflows or steps.
|
|
566
|
+
- `readStream()` can be called from anywhere.
|
|
567
|
+
- Streams are immutable and append-only.
|
|
568
|
+
- Writes from a workflow happen exactly-once. Writes from a step happen at-least-once (retried steps may write duplicates).
|
|
569
|
+
- Streams are automatically closed when the workflow terminates.
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
573
|
+
|
|
574
|
+
async function streamingWorkflow(): Promise<void> {
|
|
575
|
+
for (let i = 0; i < 10; i++) {
|
|
576
|
+
const result = await SolidActions.runStep(() => processChunk(i), { name: `chunk-${i}` });
|
|
577
|
+
await SolidActions.writeStream('progress', { step: i, result });
|
|
578
|
+
}
|
|
579
|
+
await SolidActions.closeStream('progress');
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Reader:
|
|
583
|
+
for await (const value of SolidActions.readStream(workflowID, 'progress')) {
|
|
584
|
+
console.log(`Progress: ${JSON.stringify(value)}`);
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### `SolidActions.respond()`
|
|
589
|
+
|
|
590
|
+
Sends an early response body to the external caller. Used in webhook wait-mode workflows to return a response before the workflow completes.
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
static async respond(body: unknown): Promise<void>
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
- Can only be called from a workflow function (not from steps).
|
|
597
|
+
- The body is sent back to the HTTP caller that triggered the webhook.
|
|
598
|
+
- Must be called while the webhook request is still waiting (within the webhook timeout).
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
602
|
+
|
|
603
|
+
async function webhookWorkflow(input: { query: string }): Promise<void> {
|
|
604
|
+
const quickResult = await SolidActions.runStep(() => fastLookup(input.query), { name: 'lookup' });
|
|
605
|
+
|
|
606
|
+
// Send early response to the waiting HTTP caller
|
|
607
|
+
await SolidActions.respond({ status: 'ok', data: quickResult });
|
|
608
|
+
|
|
609
|
+
// Continue with slower background processing
|
|
610
|
+
await SolidActions.runStep(() => heavyProcessing(input.query), { name: 'process' });
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### `SolidActions.getSignalUrls()`
|
|
615
|
+
|
|
616
|
+
Generates pre-built signal URLs for the current workflow. Useful for approval workflows where external users need clickable approve/reject links.
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
static getSignalUrls(topic?: string): {
|
|
620
|
+
base: string;
|
|
621
|
+
approve: string;
|
|
622
|
+
reject: string;
|
|
623
|
+
custom: (action: string) => string;
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
- Must be called from within a workflow.
|
|
628
|
+
- Returns URLs that send signals to the current workflow via the platform's signal API.
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
632
|
+
|
|
633
|
+
async function approvalWorkflow(request: { id: string }): Promise<void> {
|
|
634
|
+
const urls = SolidActions.getSignalUrls('approval');
|
|
635
|
+
|
|
636
|
+
await SolidActions.runStep(
|
|
637
|
+
() =>
|
|
638
|
+
sendEmail({
|
|
639
|
+
to: 'manager@example.com',
|
|
640
|
+
body: `Approve: ${urls.approve}\nReject: ${urls.reject}`,
|
|
641
|
+
}),
|
|
642
|
+
{ name: 'notifyManager' },
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
const decision = await SolidActions.recv<string>('approval', 86400);
|
|
646
|
+
// decision will be 'approve' or 'reject' based on which URL was clicked
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Workflow Handles
|
|
653
|
+
|
|
654
|
+
A `WorkflowHandle<R>` represents an active or completed workflow execution.
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
interface WorkflowHandle<R> {
|
|
658
|
+
get workflowID(): string;
|
|
659
|
+
getStatus(): Promise<WorkflowStatus | null>;
|
|
660
|
+
getResult(): Promise<R>;
|
|
661
|
+
getWorkflowInputs<T extends any[]>(): Promise<T>;
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### `handle.workflowID`
|
|
666
|
+
|
|
667
|
+
The unique ID of the workflow execution.
|
|
668
|
+
|
|
669
|
+
### `handle.getResult()`
|
|
670
|
+
|
|
671
|
+
Waits for the workflow to complete, then returns its result. Throws if the workflow errors.
|
|
672
|
+
|
|
673
|
+
### `handle.getStatus()`
|
|
674
|
+
|
|
675
|
+
Returns the current `WorkflowStatus` (see below).
|
|
676
|
+
|
|
677
|
+
### `handle.getWorkflowInputs()`
|
|
678
|
+
|
|
679
|
+
Returns the deserialized arguments that were passed to the workflow function.
|
|
680
|
+
|
|
681
|
+
### WorkflowStatus
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
interface WorkflowStatus {
|
|
685
|
+
readonly workflowID: string;
|
|
686
|
+
readonly status: string; // PENDING | SUCCESS | ERROR | CANCELLED | ENQUEUED | MAX_RECOVERY_ATTEMPTS_EXCEEDED
|
|
687
|
+
readonly workflowName: string;
|
|
688
|
+
readonly workflowClassName: string;
|
|
689
|
+
readonly workflowConfigName?: string;
|
|
690
|
+
readonly input?: unknown[];
|
|
691
|
+
readonly output?: unknown;
|
|
692
|
+
readonly error?: unknown;
|
|
693
|
+
readonly executorId?: string;
|
|
694
|
+
readonly applicationVersion?: string;
|
|
695
|
+
readonly recoveryAttempts?: number;
|
|
696
|
+
readonly createdAt: number; // UNIX epoch ms
|
|
697
|
+
readonly updatedAt?: number; // UNIX epoch ms
|
|
698
|
+
readonly timeoutMS?: number;
|
|
699
|
+
readonly deadlineEpochMS?: number;
|
|
700
|
+
readonly applicationID: string;
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
---
|
|
705
|
+
|
|
706
|
+
## Context Variables
|
|
707
|
+
|
|
708
|
+
These are accessible from within workflows and steps:
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
SolidActions.workflowID: string | undefined // Current workflow ID
|
|
712
|
+
SolidActions.runID: string | undefined // Current run ID
|
|
713
|
+
SolidActions.stepID: number | undefined // Current step ID within the workflow
|
|
714
|
+
SolidActions.stepStatus: StepStatus | undefined // Current step retry info
|
|
715
|
+
SolidActions.logger: DLogger // Logger instance
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### StepStatus
|
|
719
|
+
|
|
720
|
+
```typescript
|
|
721
|
+
interface StepStatus {
|
|
722
|
+
stepID: number;
|
|
723
|
+
currentAttempt?: number; // Zero-indexed retry attempt
|
|
724
|
+
maxAttempts?: number; // Total allowed attempts
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
```typescript
|
|
729
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
730
|
+
|
|
731
|
+
async function myWorkflow(): Promise<void> {
|
|
732
|
+
SolidActions.logger.info(`Workflow ${SolidActions.workflowID} started`);
|
|
733
|
+
await SolidActions.runStep(() => doWork(), { name: 'work' });
|
|
734
|
+
}
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
---
|
|
738
|
+
|
|
739
|
+
## Workflow Management
|
|
740
|
+
|
|
741
|
+
### `SolidActions.getWorkflowStatus()`
|
|
742
|
+
|
|
743
|
+
```typescript
|
|
744
|
+
static getWorkflowStatus(workflowID: string): Promise<WorkflowStatus | null>
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### `SolidActions.getResult()`
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
static async getResult<T>(workflowID: string, timeoutSeconds?: number): Promise<T | null>
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### `SolidActions.listWorkflows()`
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
static async listWorkflows(input: GetWorkflowsInput): Promise<WorkflowStatus[]>
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
```typescript
|
|
760
|
+
interface GetWorkflowsInput {
|
|
761
|
+
workflowIDs?: string[];
|
|
762
|
+
workflowName?: string;
|
|
763
|
+
status?: 'PENDING' | 'SUCCESS' | 'ERROR' | 'MAX_RECOVERY_ATTEMPTS_EXCEEDED' | 'CANCELLED' | 'ENQUEUED';
|
|
764
|
+
startTime?: string; // RFC 3339 timestamp
|
|
765
|
+
endTime?: string; // RFC 3339 timestamp
|
|
766
|
+
applicationVersion?: string;
|
|
767
|
+
limit?: number;
|
|
768
|
+
offset?: number;
|
|
769
|
+
sortDesc?: boolean;
|
|
770
|
+
}
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
775
|
+
|
|
776
|
+
const pendingWorkflows = await SolidActions.listWorkflows({
|
|
777
|
+
status: 'PENDING',
|
|
778
|
+
limit: 50,
|
|
779
|
+
sortDesc: true,
|
|
780
|
+
});
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### `SolidActions.listWorkflowSteps()`
|
|
784
|
+
|
|
785
|
+
```typescript
|
|
786
|
+
static async listWorkflowSteps(workflowID: string): Promise<StepInfo[] | undefined>
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
```typescript
|
|
790
|
+
interface StepInfo {
|
|
791
|
+
readonly functionID: number; // Zero-indexed step ID
|
|
792
|
+
readonly name: string;
|
|
793
|
+
readonly output: unknown;
|
|
794
|
+
readonly error: Error | null;
|
|
795
|
+
readonly childWorkflowID: string | null;
|
|
796
|
+
readonly startedAtEpochMs?: number;
|
|
797
|
+
readonly completedAtEpochMs?: number;
|
|
798
|
+
}
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
### `SolidActions.cancelWorkflow()`
|
|
802
|
+
|
|
803
|
+
Cancels a workflow. Sets status to `CANCELLED` and preempts execution at the next step boundary.
|
|
804
|
+
|
|
805
|
+
```typescript
|
|
806
|
+
static async cancelWorkflow(workflowID: string): Promise<void>
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
### `SolidActions.resumeWorkflow()`
|
|
810
|
+
|
|
811
|
+
Resumes a cancelled or failed workflow from its last completed step.
|
|
812
|
+
|
|
813
|
+
```typescript
|
|
814
|
+
static async resumeWorkflow<T>(workflowID: string): Promise<WorkflowHandle<Awaited<T>>>
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### `SolidActions.forkWorkflow()`
|
|
818
|
+
|
|
819
|
+
Starts a new execution of a workflow from a specific step. Useful for patching failed workflows on a new code version.
|
|
820
|
+
|
|
821
|
+
```typescript
|
|
822
|
+
static async forkWorkflow<T>(
|
|
823
|
+
workflowID: string,
|
|
824
|
+
startStep: number,
|
|
825
|
+
options?: {
|
|
826
|
+
newWorkflowID?: string;
|
|
827
|
+
applicationVersion?: string;
|
|
828
|
+
timeoutMS?: number;
|
|
829
|
+
}
|
|
830
|
+
): Promise<WorkflowHandle<Awaited<T>>>
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
```typescript
|
|
834
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
835
|
+
|
|
836
|
+
// Fork a failed workflow from step 3, running on current code version
|
|
837
|
+
const handle = await SolidActions.forkWorkflow('failed-wf-id', 3);
|
|
838
|
+
const result = await handle.getResult();
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
## Versioning and Recovery
|
|
844
|
+
|
|
845
|
+
SolidActions versions applications by hashing workflow source code at launch. Workflows are tagged with the version on which they started. During recovery, only workflows matching the current version are recovered — this prevents unsafe recovery of workflows that depend on different code.
|
|
846
|
+
|
|
847
|
+
You can override the version via `applicationVersion` in `SolidActionsConfig`.
|
|
848
|
+
|
|
849
|
+
### `SolidActions.patch()` / `SolidActions.deprecatePatch()`
|
|
850
|
+
|
|
851
|
+
Used for safe code evolution. `patch()` returns `true` if the named patch is active, allowing conditional logic based on code version.
|
|
852
|
+
|
|
853
|
+
```typescript
|
|
854
|
+
static async patch(patchName: string): Promise<boolean>
|
|
855
|
+
static async deprecatePatch(patchName: string): Promise<boolean>
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
---
|
|
859
|
+
|
|
860
|
+
## ConfiguredInstance
|
|
861
|
+
|
|
862
|
+
For class-based workflows where instances need configuration and state:
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
import { SolidActions, ConfiguredInstance } from '@solidactions/sdk';
|
|
866
|
+
|
|
867
|
+
class EmailService extends ConfiguredInstance {
|
|
868
|
+
private apiKey: string;
|
|
869
|
+
|
|
870
|
+
constructor(name: string, apiKey: string) {
|
|
871
|
+
super(name);
|
|
872
|
+
this.apiKey = apiKey;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
override async initialize(): Promise<void> {
|
|
876
|
+
// Validate configuration — called during SolidActions.launch()
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
@SolidActions.workflow()
|
|
880
|
+
async sendEmailWorkflow(to: string, subject: string): Promise<void> {
|
|
881
|
+
await SolidActions.runStep(() => this.sendEmail(to, subject), { name: 'send' });
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
private async sendEmail(to: string, subject: string): Promise<void> {
|
|
885
|
+
// Use this.apiKey to send email
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Must instantiate before SolidActions.launch()
|
|
890
|
+
const emailService = new EmailService('primary-email', process.env.EMAIL_API_KEY!);
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
Use `@SolidActions.className(name)` to set a custom class name for the registry:
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
@SolidActions.className('email-svc')
|
|
897
|
+
class EmailService extends ConfiguredInstance { ... }
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
**Prefer `registerWorkflow` with plain functions over `ConfiguredInstance` when possible.** Use `ConfiguredInstance` only when you need instance-level configuration.
|
|
901
|
+
|
|
902
|
+
---
|
|
903
|
+
|
|
904
|
+
## SolidActionsClient
|
|
905
|
+
|
|
906
|
+
A standalone HTTP client for querying and interacting with workflows from outside the SDK runtime. Useful for external services, API servers, or monitoring tools.
|
|
907
|
+
|
|
908
|
+
```typescript
|
|
909
|
+
import { SolidActionsClient } from '@solidactions/sdk';
|
|
910
|
+
|
|
911
|
+
const client = SolidActionsClient.create({
|
|
912
|
+
httpConfig: {
|
|
913
|
+
apiUrl: process.env.SOLIDACTIONS_API_URL!,
|
|
914
|
+
apiKey: process.env.SOLIDACTIONS_API_KEY!,
|
|
915
|
+
},
|
|
916
|
+
});
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
### Methods
|
|
920
|
+
|
|
921
|
+
```typescript
|
|
922
|
+
// Retrieve a workflow handle
|
|
923
|
+
client.retrieveWorkflow<T>(workflowID: string): WorkflowHandle<Awaited<T>>
|
|
924
|
+
|
|
925
|
+
// Send a message to a workflow
|
|
926
|
+
client.send<T>(destinationID: string, message: T, topic?: string, idempotencyKey?: string): Promise<void>
|
|
927
|
+
|
|
928
|
+
// Get a workflow event
|
|
929
|
+
client.getEvent<T>(workflowID: string, key: string, timeoutSeconds?: number): Promise<T | null>
|
|
930
|
+
|
|
931
|
+
// Cancel a workflow
|
|
932
|
+
client.cancelWorkflow(workflowID: string): Promise<void>
|
|
933
|
+
|
|
934
|
+
// Resume a workflow
|
|
935
|
+
client.resumeWorkflow(workflowID: string): Promise<void>
|
|
936
|
+
|
|
937
|
+
// Fork a workflow
|
|
938
|
+
client.forkWorkflow(workflowID: string, startStep: number, options?: { newWorkflowID?: string; applicationVersion?: string; timeoutMS?: number }): Promise<string>
|
|
939
|
+
|
|
940
|
+
// Get workflow status
|
|
941
|
+
client.getWorkflow(workflowID: string): Promise<WorkflowStatus | undefined>
|
|
942
|
+
|
|
943
|
+
// List workflows
|
|
944
|
+
client.listWorkflows(input: GetWorkflowsInput): Promise<WorkflowStatus[]>
|
|
945
|
+
|
|
946
|
+
// List workflow steps
|
|
947
|
+
client.listWorkflowSteps(workflowID: string): Promise<StepInfo[] | undefined>
|
|
948
|
+
|
|
949
|
+
// Read a stream
|
|
950
|
+
client.readStream<T>(workflowID: string, key: string): AsyncGenerator<T, void, unknown>
|
|
951
|
+
|
|
952
|
+
// Clean up
|
|
953
|
+
client.destroy(): Promise<void>
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
```typescript
|
|
957
|
+
import { SolidActionsClient } from '@solidactions/sdk';
|
|
958
|
+
|
|
959
|
+
const client = SolidActionsClient.create({
|
|
960
|
+
httpConfig: {
|
|
961
|
+
apiUrl: 'https://app.solidactions.com/api/internal',
|
|
962
|
+
apiKey: 'sa_key_...',
|
|
963
|
+
},
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
// Check workflow status
|
|
967
|
+
const status = await client.getWorkflow('wf-123');
|
|
968
|
+
console.log(status?.status); // 'SUCCESS'
|
|
969
|
+
|
|
970
|
+
// Send approval signal
|
|
971
|
+
await client.send('wf-456', 'approved', 'approval');
|
|
972
|
+
|
|
973
|
+
// Stream results
|
|
974
|
+
for await (const chunk of client.readStream('wf-789', 'output')) {
|
|
975
|
+
console.log(chunk);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
await client.destroy();
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
---
|
|
982
|
+
|
|
983
|
+
## Configuration
|
|
984
|
+
|
|
985
|
+
### SolidActionsConfig
|
|
986
|
+
|
|
987
|
+
Passed to `SolidActions.setConfig()`:
|
|
988
|
+
|
|
989
|
+
```typescript
|
|
990
|
+
interface SolidActionsConfig {
|
|
991
|
+
name?: string; // Application name
|
|
992
|
+
|
|
993
|
+
api?: {
|
|
994
|
+
url: string; // Base API URL
|
|
995
|
+
key: string; // API key / Bearer token
|
|
996
|
+
timeout?: number; // Request timeout in ms (default: 30000)
|
|
997
|
+
maxRetries?: number; // Max retry attempts (default: 3)
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
enableOTLP?: boolean; // Enable OpenTelemetry (default: false)
|
|
1001
|
+
logLevel?: string; // Log level (default: 'info')
|
|
1002
|
+
otlpTracesEndpoints?: string[]; // OTLP trace receivers
|
|
1003
|
+
otlpLogsEndpoints?: string[]; // OTLP log receivers
|
|
1004
|
+
|
|
1005
|
+
runAdminServer?: boolean; // Run admin HTTP server (default: true)
|
|
1006
|
+
adminPort?: number; // Admin server port (default: 3001)
|
|
1007
|
+
|
|
1008
|
+
applicationVersion?: string; // Override auto-computed version
|
|
1009
|
+
}
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
**Note:** There is no `systemDatabaseUrl`. The SDK communicates with the SolidActions platform via HTTP API (`api.url` and `api.key`).
|
|
1013
|
+
|
|
1014
|
+
---
|
|
1015
|
+
|
|
1016
|
+
## Custom Serialization
|
|
1017
|
+
|
|
1018
|
+
Register custom serializers for non-JSON-serializable types:
|
|
1019
|
+
|
|
1020
|
+
```typescript
|
|
1021
|
+
static registerSerialization<T, S extends JSONValue>(recipe: SerializationRecipe<T, S>): void
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
```typescript
|
|
1025
|
+
interface SerializationRecipe<T, S> {
|
|
1026
|
+
name: string;
|
|
1027
|
+
isApplicable: (v: unknown) => v is T;
|
|
1028
|
+
serialize: (v: T) => S;
|
|
1029
|
+
deserialize: (s: S) => T;
|
|
1030
|
+
}
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
```typescript
|
|
1034
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
1035
|
+
|
|
1036
|
+
SolidActions.registerSerialization({
|
|
1037
|
+
name: 'BigInt',
|
|
1038
|
+
isApplicable: (v): v is bigint => typeof v === 'bigint',
|
|
1039
|
+
serialize: (v) => v.toString(),
|
|
1040
|
+
deserialize: (s) => BigInt(s),
|
|
1041
|
+
});
|
|
1042
|
+
```
|
|
1043
|
+
|
|
1044
|
+
---
|
|
1045
|
+
|
|
1046
|
+
## Logging
|
|
1047
|
+
|
|
1048
|
+
Use `SolidActions.logger` for structured logging within workflows and steps:
|
|
1049
|
+
|
|
1050
|
+
```typescript
|
|
1051
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
1052
|
+
|
|
1053
|
+
SolidActions.logger.info('Processing started');
|
|
1054
|
+
SolidActions.logger.warn('Rate limit approaching');
|
|
1055
|
+
SolidActions.logger.error(`Error: ${(error as Error).message}`);
|
|
1056
|
+
SolidActions.logger.debug('Step details', { stepId: SolidActions.stepID });
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
---
|
|
1060
|
+
|
|
1061
|
+
## Local Development
|
|
1062
|
+
|
|
1063
|
+
Run workflows locally without deploying to SolidActions using the in-memory mock server. This is ideal for fast iteration on workflow logic.
|
|
1064
|
+
|
|
1065
|
+
### Using the CLI (recommended)
|
|
1066
|
+
|
|
1067
|
+
```bash
|
|
1068
|
+
npm install -g @solidactions/cli
|
|
1069
|
+
|
|
1070
|
+
# Run a workflow locally — no deploy, no auth, no backend needed
|
|
1071
|
+
solidactions dev src/my-workflow.ts -i '{"key": "value"}'
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
The `dev` command starts an in-memory mock server, sets the required SDK environment variables, and runs your workflow file directly. All step execution works normally — only platform features like durable sleep wakeups and cross-process messaging are no-ops.
|
|
1075
|
+
|
|
1076
|
+
### Using the mock server directly
|
|
1077
|
+
|
|
1078
|
+
Import `createMockServer` from `@solidactions/sdk/testing` for custom test setups:
|
|
1079
|
+
|
|
1080
|
+
```typescript
|
|
1081
|
+
import { createMockServer } from '@solidactions/sdk/testing';
|
|
1082
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
1083
|
+
|
|
1084
|
+
const server = await createMockServer();
|
|
1085
|
+
|
|
1086
|
+
SolidActions.setConfig({
|
|
1087
|
+
name: 'test-app',
|
|
1088
|
+
api: { url: server.baseUrl, key: 'test-key' },
|
|
1089
|
+
});
|
|
1090
|
+
await SolidActions.launch();
|
|
1091
|
+
|
|
1092
|
+
// Run your workflow...
|
|
1093
|
+
|
|
1094
|
+
await SolidActions.shutdown();
|
|
1095
|
+
await server.stop();
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
The mock server implements the full SolidActions HTTP API in memory — workflows, steps, messages, events, streams, and queues all work.
|
|
1099
|
+
|
|
1100
|
+
### What works locally vs what doesn't
|
|
1101
|
+
|
|
1102
|
+
| Works | No-op locally |
|
|
1103
|
+
| ------------------------------ | ------------------------------- |
|
|
1104
|
+
| Sequential & parallel steps | Durable sleep scheduler wakeups |
|
|
1105
|
+
| Child workflows | Cross-process messaging |
|
|
1106
|
+
| Events (`setEvent`/`getEvent`) | Tenant env var injection |
|
|
1107
|
+
| Streams | Webhook `respond()` |
|
|
1108
|
+
| Retries with backoff | Persistent recovery after crash |
|
|
1109
|
+
|
|
1110
|
+
## Testing
|
|
1111
|
+
|
|
1112
|
+
Use Jest or Vitest with the mock server for test isolation (no real backend needed):
|
|
1113
|
+
|
|
1114
|
+
```typescript
|
|
1115
|
+
import { createMockServer, MockHttpServer } from '@solidactions/sdk/testing';
|
|
1116
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
1117
|
+
|
|
1118
|
+
let server: MockHttpServer;
|
|
1119
|
+
|
|
1120
|
+
beforeAll(async () => {
|
|
1121
|
+
server = await createMockServer();
|
|
1122
|
+
SolidActions.setConfig({
|
|
1123
|
+
name: 'test-app',
|
|
1124
|
+
api: { url: server.baseUrl, key: 'test-key' },
|
|
1125
|
+
});
|
|
1126
|
+
await SolidActions.launch();
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
afterAll(async () => {
|
|
1130
|
+
await SolidActions.shutdown();
|
|
1131
|
+
await server.stop();
|
|
1132
|
+
});
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
For test isolation of event receivers:
|
|
1136
|
+
|
|
1137
|
+
```typescript
|
|
1138
|
+
import { SolidActions } from '@solidactions/sdk';
|
|
1139
|
+
|
|
1140
|
+
beforeEach(async () => {
|
|
1141
|
+
await SolidActions.deactivateEventReceivers();
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
afterEach(async () => {
|
|
1145
|
+
await SolidActions.initEventReceivers();
|
|
1146
|
+
});
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
---
|
|
1150
|
+
|
|
1151
|
+
## Error Classes
|
|
1152
|
+
|
|
1153
|
+
All SDK errors extend `SolidActionsError`:
|
|
1154
|
+
|
|
1155
|
+
```typescript
|
|
1156
|
+
import { SolidActionsError } from '@solidactions/sdk';
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
| Error Class | When Thrown |
|
|
1160
|
+
| ---------------------------------------------- | ------------------------------------------------------------- |
|
|
1161
|
+
| `SolidActionsWorkflowConflictError` | Workflow ID already exists with different code |
|
|
1162
|
+
| `SolidActionsMaxStepRetriesError` | Step exceeded maximum retry attempts (has `.errors: Error[]`) |
|
|
1163
|
+
| `SolidActionsWorkflowCancelledError` | Workflow was cancelled (has `.workflowID: string`) |
|
|
1164
|
+
| `SolidActionsMaxRecoveryAttemptsExceededError` | Workflow exceeded max recovery attempts |
|
|
1165
|
+
| `SolidActionsNotRegisteredError` | Referenced workflow/step not registered |
|
|
1166
|
+
| `SolidActionsInitializationError` | SDK failed to initialize |
|
|
1167
|
+
| `SolidActionsNonExistentWorkflowError` | Workflow ID not found |
|
|
1168
|
+
| `SolidActionsConflictingRegistrationError` | Duplicate workflow/step name |
|
|
1169
|
+
| `SolidActionsUnexpectedStepError` | Step executed in wrong order during recovery |
|
|
1170
|
+
| `SolidActionsAwaitedWorkflowCancelledError` | Awaited child workflow was cancelled |
|
|
1171
|
+
| `SolidActionsHttpError` | HTTP communication error (base class) |
|
|
1172
|
+
| `SolidActionsUnauthorizedError` | 401 response |
|
|
1173
|
+
| `SolidActionsForbiddenError` | 403 response |
|
|
1174
|
+
| `SolidActionsNotFoundError` | 404 response |
|
|
1175
|
+
| `SolidActionsRateLimitedError` | 429 response (has `.retryAfterSeconds?: number`) |
|
|
1176
|
+
| `SolidActionsServerError` | 5xx response |
|
|
1177
|
+
| `SolidActionsNetworkError` | Network connectivity failure |
|
|
1178
|
+
|
|
1179
|
+
---
|
|
1180
|
+
|
|
1181
|
+
## Rules for AI Consumers
|
|
1182
|
+
|
|
1183
|
+
### Do
|
|
1184
|
+
|
|
1185
|
+
- Import everything from `@solidactions/sdk`
|
|
1186
|
+
- Use `SolidActions.runStep()` for all non-deterministic operations
|
|
1187
|
+
- Use `SolidActions.run()` as the entry point for platform workflows
|
|
1188
|
+
- Use `Promise.allSettled()` for parallel step execution
|
|
1189
|
+
- Keep workflow functions deterministic
|
|
1190
|
+
- Use `SolidActions.now()` instead of `Date.now()` in workflows
|
|
1191
|
+
- Use `SolidActions.randomUUID()` instead of `crypto.randomUUID()` in workflows
|
|
1192
|
+
- Use `SolidActions.sleep()` instead of `setTimeout` for delays
|
|
1193
|
+
- Fully type all workflow and step function signatures
|
|
1194
|
+
- Await all promises
|
|
1195
|
+
|
|
1196
|
+
### Do Not
|
|
1197
|
+
|
|
1198
|
+
- Do not call context methods (`send`, `recv`, `setEvent`, `getEvent`, `sleep`, `startWorkflow`) from inside a step
|
|
1199
|
+
- Do not start workflows from inside a step
|
|
1200
|
+
- Do not use `Promise.all()` — use `Promise.allSettled()`
|
|
1201
|
+
- Do not perform non-deterministic operations directly in workflow functions
|
|
1202
|
+
- Do not use `systemDatabaseUrl` — the SDK uses HTTP API configuration
|
|
1203
|
+
- Do not reference `WorkflowQueue`, `Debouncer`, `registerScheduled`, or `@SolidActions.scheduled()` — these features do not exist
|
|
1204
|
+
- Do not import from `@dbos-inc/dbos-sdk` — use `@solidactions/sdk`
|
|
1205
|
+
- Do not reference `Toolbox`, `koaContext`, `getApi`, `postApi`, or `SolidActions Transact`
|
|
1206
|
+
- Do not create or update global variables from workflows or steps
|
|
1207
|
+
- Do not call `SolidActions.setEvent` or `SolidActions.recv` from outside a workflow function
|