@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.
Files changed (52) hide show
  1. package/README.md +25 -4
  2. package/dist/src/cli/cli.js +0 -0
  3. package/dist/src/testing/index.d.ts +13 -0
  4. package/dist/src/testing/index.d.ts.map +1 -0
  5. package/dist/src/testing/index.js +19 -0
  6. package/dist/src/testing/index.js.map +1 -0
  7. package/dist/src/testing/mock_server.d.ts +134 -0
  8. package/dist/src/testing/mock_server.d.ts.map +1 -0
  9. package/dist/src/testing/mock_server.js +613 -0
  10. package/dist/src/testing/mock_server.js.map +1 -0
  11. package/dist/tsconfig.tsbuildinfo +1 -1
  12. package/docs/sdk-reference.md +1207 -0
  13. package/package.json +6 -1
  14. package/.claude/settings.local.json +0 -7
  15. package/dist/dbos-config.schema.json +0 -132
  16. package/dist/src/cli/commands.d.ts +0 -3
  17. package/dist/src/cli/commands.d.ts.map +0 -1
  18. package/dist/src/cli/commands.js +0 -46
  19. package/dist/src/cli/commands.js.map +0 -1
  20. package/dist/src/datasource.d.ts +0 -109
  21. package/dist/src/datasource.d.ts.map +0 -1
  22. package/dist/src/datasource.js +0 -204
  23. package/dist/src/datasource.js.map +0 -1
  24. package/dist/src/dbos-executor.d.ts +0 -189
  25. package/dist/src/dbos-executor.d.ts.map +0 -1
  26. package/dist/src/dbos-executor.js +0 -817
  27. package/dist/src/dbos-executor.js.map +0 -1
  28. package/dist/src/dbos.d.ts +0 -519
  29. package/dist/src/dbos.d.ts.map +0 -1
  30. package/dist/src/dbos.js +0 -1282
  31. package/dist/src/dbos.js.map +0 -1
  32. package/dist/src/debouncer.d.ts +0 -33
  33. package/dist/src/debouncer.d.ts.map +0 -1
  34. package/dist/src/debouncer.js +0 -170
  35. package/dist/src/debouncer.js.map +0 -1
  36. package/dist/src/scheduler/crontab.d.ts +0 -14
  37. package/dist/src/scheduler/crontab.d.ts.map +0 -1
  38. package/dist/src/scheduler/crontab.js +0 -308
  39. package/dist/src/scheduler/crontab.js.map +0 -1
  40. package/dist/src/scheduler/scheduler.d.ts +0 -41
  41. package/dist/src/scheduler/scheduler.d.ts.map +0 -1
  42. package/dist/src/scheduler/scheduler.js +0 -165
  43. package/dist/src/scheduler/scheduler.js.map +0 -1
  44. package/dist/src/wfqueue.d.ts +0 -64
  45. package/dist/src/wfqueue.d.ts.map +0 -1
  46. package/dist/src/wfqueue.js +0 -147
  47. package/dist/src/wfqueue.js.map +0 -1
  48. package/docs/api-schema.md +0 -1441
  49. package/docs/migration-guide.md +0 -460
  50. package/docs/phase-14-changes.md +0 -156
  51. package/docs/solidsteps-ai-prompt.md +0 -534
  52. 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