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