@elevasis/sdk 0.5.13 → 0.5.15

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.
@@ -1,354 +1,355 @@
1
- ---
2
- title: Common Patterns
3
- description: Common resource patterns for Elevasis SDK developers -- sequential steps, conditional branching, platform tools, error handling, and resource status management
4
- loadWhen: "Building or modifying a workflow"
5
- ---
6
-
7
- This page collects the patterns you will reach for most often when writing resources. Each pattern is self-contained and can be adapted directly into your project.
8
-
9
- ---
10
-
11
- ## Sequential Workflow Steps
12
-
13
- The simplest pattern: a chain of steps where each step feeds its output into the next.
14
-
15
- ```typescript
16
- import { z } from 'zod';
17
- import { StepType } from '@elevasis/sdk';
18
- import type { WorkflowDefinition, WorkflowStep } from '@elevasis/sdk';
19
-
20
- const inputSchema = z.object({ orderId: z.string() });
21
- const outputSchema = z.object({ shipped: z.boolean(), trackingNumber: z.string() });
22
-
23
- type Input = z.infer<typeof inputSchema>;
24
- type Output = z.infer<typeof outputSchema>;
25
-
26
- const validateStep: WorkflowStep = {
27
- type: StepType.LINEAR,
28
- handler: async (input: Input) => {
29
- const order = await getOrder(input.orderId);
30
- if (!order) throw new Error(`Order ${input.orderId} not found`);
31
- return { order };
32
- },
33
- next: { target: 'ship' },
34
- };
35
-
36
- const shipStep: WorkflowStep = {
37
- type: StepType.LINEAR,
38
- handler: async (input) => {
39
- const tracking = await createShipment(input.order);
40
- return { shipped: true, trackingNumber: tracking.number };
41
- },
42
- next: null, // terminal -- no further steps
43
- };
44
-
45
- const fulfillOrder: WorkflowDefinition = {
46
- config: { name: 'fulfill-order', description: 'Validates and ships an order', status: 'dev' },
47
- contract: { input: inputSchema, output: outputSchema },
48
- steps: { validate: validateStep, ship: shipStep },
49
- entryPoint: 'validate',
50
- };
51
- ```
52
-
53
- **Key points:**
54
-
55
- - `next: { target: 'stepName' }` routes to the next step
56
- - `next: null` marks the terminal step
57
- - Each step receives the full return value of the previous step as its `input`
58
- - The terminal step's return value must satisfy `contract.output`
59
-
60
- ---
61
-
62
- ## Conditional Branching
63
-
64
- Use `StepType.CONDITIONAL` when the next step depends on the output of the current step.
65
-
66
- ```typescript
67
- import { StepType } from '@elevasis/sdk';
68
- import type { WorkflowStep } from '@elevasis/sdk';
69
-
70
- const scoreStep: WorkflowStep = {
71
- type: StepType.CONDITIONAL,
72
- handler: async (input) => {
73
- const score = await calculateRiskScore(input.applicationId);
74
- return { score, applicationId: input.applicationId };
75
- },
76
- next: {
77
- routes: [
78
- {
79
- condition: (output) => output.score \>= 80,
80
- target: 'autoApprove',
81
- },
82
- {
83
- condition: (output) => output.score \>= 40,
84
- target: 'manualReview',
85
- },
86
- ],
87
- defaultTarget: 'autoReject', // used when no condition matches
88
- },
89
- };
90
- ```
91
-
92
- **Key points:**
93
-
94
- - Routes are evaluated in order -- the first matching condition wins
95
- - `defaultTarget` is required and acts as the `else` branch
96
- - The condition function receives the full handler output
97
- - All route targets and `defaultTarget` must be keys in your `steps` record
98
-
99
- ---
100
-
101
- ## Using Platform Tools in Steps
102
-
103
- Platform tools let your steps call integrations managed by Elevasis (email, CRM, databases, etc.). Import `platform` from `@elevasis/sdk/worker` and call it with the tool name, method, parameters, and a credential reference.
104
-
105
- ```typescript
106
- import { platform, PlatformToolError } from '@elevasis/sdk/worker';
107
- import type { WorkflowStep } from '@elevasis/sdk';
108
- import { StepType } from '@elevasis/sdk';
109
-
110
- const sendEmailStep: WorkflowStep = {
111
- type: StepType.LINEAR,
112
- handler: async (input, context) => {
113
- const result = await platform.call({
114
- tool: 'email',
115
- method: 'send',
116
- params: {
117
- to: input.recipientEmail,
118
- subject: input.subject,
119
- body: input.body,
120
- },
121
- credential: 'sendgrid', // name of the stored credential
122
- });
123
-
124
- context.logger.info('Email sent', { messageId: result.messageId });
125
- return { sent: true, messageId: result.messageId };
126
- },
127
- next: null,
128
- };
129
- ```
130
-
131
- **Key points:**
132
-
133
- - `platform.call()` is async and times out after 60 seconds
134
- - `credential` is the name of a platform environment variable set via `elevasis-sdk env set`
135
- - On failure, `platform.call()` throws `PlatformToolError` (not `ToolingError`)
136
- - Always log success so executions are easy to debug in the dashboard
137
-
138
- ---
139
-
140
- ## Error Handling
141
-
142
- ### Catching Tool Errors
143
-
144
- Use `PlatformToolError` (from `@elevasis/sdk/worker`) to handle tool-specific failures without catching everything:
145
-
146
- ```typescript
147
- import { platform, PlatformToolError } from '@elevasis/sdk/worker';
148
-
149
- const step = async (input) => {
150
- try {
151
- const result = await platform.call({
152
- tool: 'crm',
153
- method: 'createContact',
154
- params: { email: input.email, name: input.name },
155
- credential: 'CRM_API_KEY',
156
- });
157
- return { contactId: result.id };
158
- } catch (err) {
159
- if (err instanceof PlatformToolError) {
160
- // Tool failed -- log it and return a degraded result
161
- console.error('CRM tool failed:', err.message);
162
- return { contactId: null, error: err.message };
163
- }
164
- throw err; // re-throw unexpected errors
165
- }
166
- };
167
- ```
168
-
169
- ### Failing an Execution Explicitly
170
-
171
- Use `ExecutionError` when your step detects a condition that should mark the entire execution as failed:
172
-
173
- ```typescript
174
- import { ExecutionError } from '@elevasis/sdk';
175
-
176
- const validateStep = async (input) => {
177
- if (!input.userId) {
178
- throw new ExecutionError('userId is required', { code: 'MISSING_INPUT' });
179
- }
180
- if (input.amount \<= 0) {
181
- throw new ExecutionError('amount must be positive', { code: 'INVALID_AMOUNT' });
182
- }
183
- return { valid: true };
184
- };
185
- ```
186
-
187
- `ExecutionError` messages and metadata appear in the Elevasis dashboard under the failed execution's detail view.
188
-
189
- ### Using ToolingError
190
-
191
- `ToolingError` is thrown by lower-level platform operations (not `platform.call()` directly). You may encounter it in advanced scenarios:
192
-
193
- ```typescript
194
- import { ToolingError } from '@elevasis/sdk';
195
-
196
- const step = async (input) => {
197
- try {
198
- return await doSomething(input);
199
- } catch (err) {
200
- if (err instanceof ToolingError) {
201
- // check err.type for the error category
202
- console.error('Tooling error:', err.type, err.message);
203
- }
204
- throw err;
205
- }
206
- };
207
- ```
208
-
209
- ---
210
-
211
- ## Logging in Steps
212
-
213
- The `context.logger` writes structured logs attached to the execution. Use it instead of `console.log` so logs appear in the dashboard alongside the execution record.
214
-
215
- ```typescript
216
- import type { StepHandler } from '@elevasis/sdk';
217
-
218
- const processStep: StepHandler = async (input, context) => {
219
- context.logger.info('Starting process', { userId: input.userId });
220
-
221
- const result = await doWork(input);
222
-
223
- context.logger.info('Process complete', { resultId: result.id });
224
- return result;
225
- };
226
- ```
227
-
228
- Avoid logging sensitive values (API keys, passwords, PII) since logs are stored and visible in the dashboard.
229
-
230
- ---
231
-
232
- ## Using the Execution Store
233
-
234
- `context.store` is a simple key-value store scoped to the current execution. Use it to pass data between steps without coupling step interfaces, or to checkpoint long-running work.
235
-
236
- ```typescript
237
- const firstStep: StepHandler = async (input, context) => {
238
- const data = await fetchExpensiveData(input.id);
239
-
240
- // Save for use by later steps
241
- await context.store.set('fetchedData', JSON.stringify(data));
242
-
243
- return { fetched: true };
244
- };
245
-
246
- const secondStep: StepHandler = async (input, context) => {
247
- const raw = await context.store.get('fetchedData');
248
- const data = JSON.parse(raw ?? '{}');
249
-
250
- return { processed: transform(data) };
251
- };
252
- ```
253
-
254
- Store values are strings. Serialize objects with `JSON.stringify` and parse with `JSON.parse`.
255
-
256
- ---
257
-
258
- ## Resource Status
259
-
260
- ### Dev vs Production
261
-
262
- While building a resource, set `config.status` to `'dev'`:
263
-
264
- ```typescript
265
- const myWorkflow: WorkflowDefinition = {
266
- config: {
267
- name: 'my-workflow',
268
- description: 'Does something useful',
269
- status: 'dev', // only manually triggerable
270
- },
271
- // ...
272
- };
273
- ```
274
-
275
- Dev resources:
276
-
277
- - Appear in `elevasis-sdk resources` output
278
- - Can be triggered with `elevasis-sdk exec my-workflow --input '{...}'`
279
- - Will NOT receive scheduled or webhook-triggered executions
280
- - Will NOT appear as available to external callers
281
-
282
- When you are ready to go live, change to `'production'` and redeploy:
283
-
284
- ```typescript
285
- config: {
286
- name: 'my-workflow',
287
- description: 'Does something useful',
288
- status: 'production', // receives all execution sources
289
- },
290
- ```
291
-
292
- ### Global Default Status
293
-
294
- Set a project-wide default in `elevasis.config.ts` to keep all resources in `'dev'` mode during development without touching each resource file:
295
-
296
- ```typescript
297
- import type { ElevasConfig } from '@elevasis/sdk';
298
-
299
- const config: ElevasConfig = {
300
- defaultStatus: 'dev',
301
- };
302
-
303
- export default config;
304
- ```
305
-
306
- Individual resources that set their own `config.status` override this default.
307
-
308
- ---
309
-
310
- ## Organizing Multiple Resources
311
-
312
- As your project grows, organize resources by business domain. Each domain gets its own directory with an `index.ts` barrel that exports `workflows` and `agents` arrays:
313
-
314
- ```typescript
315
- // src/orders/fulfill-order.ts
316
- export const fulfillOrder: WorkflowDefinition = { ... };
317
-
318
- // src/billing/send-invoice.ts
319
- export const sendInvoice: WorkflowDefinition = { ... };
320
-
321
- // src/orders/index.ts
322
- import { fulfillOrder } from './fulfill-order.js';
323
- export const workflows = [fulfillOrder];
324
- export const agents = [];
325
-
326
- // src/billing/index.ts
327
- import { sendInvoice } from './send-invoice.js';
328
- export const workflows = [sendInvoice];
329
- export const agents = [];
330
-
331
- // src/index.ts
332
- import type { OrganizationResources } from '@elevasis/sdk';
333
- import * as orders from './orders/index.js';
334
- import * as billing from './billing/index.js';
335
-
336
- const org: OrganizationResources = {
337
- workflows: [
338
- ...orders.workflows,
339
- ...billing.workflows,
340
- ],
341
- agents: [
342
- ...orders.agents,
343
- ...billing.agents,
344
- ],
345
- };
346
-
347
- export default org;
348
- ```
349
-
350
- The keys in `workflows` and `agents` are the resource identifiers used in CLI commands and the dashboard. Choose names that are descriptive and use kebab-case for consistency.
351
-
352
- ---
353
-
354
- **Last Updated:** 2026-02-25
1
+ ---
2
+ title: Common Patterns
3
+ description: Common resource patterns for Elevasis SDK developers -- sequential steps, conditional branching, platform tools, error handling, and resource status management
4
+ loadWhen: "Building or modifying a workflow"
5
+ ---
6
+
7
+ This page collects the patterns you will reach for most often when writing resources. Each pattern is self-contained and can be adapted directly into your project.
8
+
9
+ ---
10
+
11
+ ## Sequential Workflow Steps
12
+
13
+ The simplest pattern: a chain of steps where each step feeds its output into the next.
14
+
15
+ ```typescript
16
+ import { z } from 'zod';
17
+ import { StepType } from '@elevasis/sdk';
18
+ import type { WorkflowDefinition, WorkflowStep } from '@elevasis/sdk';
19
+
20
+ const inputSchema = z.object({ orderId: z.string() });
21
+ const outputSchema = z.object({ shipped: z.boolean(), trackingNumber: z.string() });
22
+
23
+ type Input = z.infer<typeof inputSchema>;
24
+ type Output = z.infer<typeof outputSchema>;
25
+
26
+ const validateStep: WorkflowStep = {
27
+ type: StepType.LINEAR,
28
+ handler: async (input: Input) => {
29
+ const order = await getOrder(input.orderId);
30
+ if (!order) throw new Error(`Order ${input.orderId} not found`);
31
+ return { order };
32
+ },
33
+ next: { type: 'linear', target: 'ship' },
34
+ };
35
+
36
+ const shipStep: WorkflowStep = {
37
+ type: StepType.LINEAR,
38
+ handler: async (input) => {
39
+ const tracking = await createShipment(input.order);
40
+ return { shipped: true, trackingNumber: tracking.number };
41
+ },
42
+ next: null, // terminal -- no further steps
43
+ };
44
+
45
+ const fulfillOrder: WorkflowDefinition = {
46
+ config: { resourceId: 'fulfill-order', name: 'Fulfill Order', type: 'workflow', description: 'Validates and ships an order', version: '1.0.0', status: 'dev' },
47
+ contract: { inputSchema, outputSchema },
48
+ steps: { validate: validateStep, ship: shipStep },
49
+ entryPoint: 'validate',
50
+ };
51
+ ```
52
+
53
+ **Key points:**
54
+
55
+ - `next: { target: 'stepName' }` routes to the next step
56
+ - `next: null` marks the terminal step
57
+ - Each step receives the full return value of the previous step as its `input`
58
+ - The terminal step's return value must satisfy `contract.output`
59
+
60
+ ---
61
+
62
+ ## Conditional Branching
63
+
64
+ Use `StepType.CONDITIONAL` when the next step depends on the output of the current step.
65
+
66
+ ```typescript
67
+ import { StepType } from '@elevasis/sdk';
68
+ import type { WorkflowStep } from '@elevasis/sdk';
69
+
70
+ const scoreStep: WorkflowStep = {
71
+ type: StepType.CONDITIONAL,
72
+ handler: async (input) => {
73
+ const score = await calculateRiskScore(input.applicationId);
74
+ return { score, applicationId: input.applicationId };
75
+ },
76
+ next: {
77
+ type: 'conditional',
78
+ routes: [
79
+ {
80
+ condition: (output) => output.score \>= 80,
81
+ target: 'autoApprove',
82
+ },
83
+ {
84
+ condition: (output) => output.score \>= 40,
85
+ target: 'manualReview',
86
+ },
87
+ ],
88
+ default: 'autoReject', // used when no condition matches
89
+ },
90
+ };
91
+ ```
92
+
93
+ **Key points:**
94
+
95
+ - Routes are evaluated in order -- the first matching condition wins
96
+ - `default` is required and acts as the `else` branch
97
+ - The condition function receives the full handler output
98
+ - All route targets and `default` must be keys in your `steps` record
99
+
100
+ ---
101
+
102
+ ## Using Platform Tools in Steps
103
+
104
+ Platform tools let your steps call integrations managed by Elevasis (email, CRM, databases, etc.). Import `platform` from `@elevasis/sdk/worker` and call it with the tool name, method, parameters, and a credential reference.
105
+
106
+ ```typescript
107
+ import { platform, PlatformToolError } from '@elevasis/sdk/worker';
108
+ import type { WorkflowStep } from '@elevasis/sdk';
109
+ import { StepType } from '@elevasis/sdk';
110
+
111
+ const sendEmailStep: WorkflowStep = {
112
+ type: StepType.LINEAR,
113
+ handler: async (input, context) => {
114
+ const result = await platform.call({
115
+ tool: 'email',
116
+ method: 'send',
117
+ params: {
118
+ to: input.recipientEmail,
119
+ subject: input.subject,
120
+ body: input.body,
121
+ },
122
+ credential: 'sendgrid', // name of the stored credential
123
+ });
124
+
125
+ context.logger.info('Email sent', { messageId: result.messageId });
126
+ return { sent: true, messageId: result.messageId };
127
+ },
128
+ next: null,
129
+ };
130
+ ```
131
+
132
+ **Key points:**
133
+
134
+ - `platform.call()` is async and times out after 60 seconds
135
+ - `credential` is the name of a platform environment variable set via `elevasis-sdk env set`
136
+ - On failure, `platform.call()` throws `PlatformToolError` (not `ToolingError`)
137
+ - Always log success so executions are easy to debug in the dashboard
138
+
139
+ ---
140
+
141
+ ## Error Handling
142
+
143
+ ### Catching Tool Errors
144
+
145
+ Use `PlatformToolError` (from `@elevasis/sdk/worker`) to handle tool-specific failures without catching everything:
146
+
147
+ ```typescript
148
+ import { platform, PlatformToolError } from '@elevasis/sdk/worker';
149
+
150
+ const step = async (input) => {
151
+ try {
152
+ const result = await platform.call({
153
+ tool: 'crm',
154
+ method: 'createContact',
155
+ params: { email: input.email, name: input.name },
156
+ credential: 'CRM_API_KEY',
157
+ });
158
+ return { contactId: result.id };
159
+ } catch (err) {
160
+ if (err instanceof PlatformToolError) {
161
+ // Tool failed -- log it and return a degraded result
162
+ console.error('CRM tool failed:', err.message);
163
+ return { contactId: null, error: err.message };
164
+ }
165
+ throw err; // re-throw unexpected errors
166
+ }
167
+ };
168
+ ```
169
+
170
+ ### Failing an Execution Explicitly
171
+
172
+ Use `ExecutionError` when your step detects a condition that should mark the entire execution as failed:
173
+
174
+ ```typescript
175
+ import { ExecutionError } from '@elevasis/sdk';
176
+
177
+ const validateStep = async (input) => {
178
+ if (!input.userId) {
179
+ throw new ExecutionError('userId is required', { code: 'MISSING_INPUT' });
180
+ }
181
+ if (input.amount \<= 0) {
182
+ throw new ExecutionError('amount must be positive', { code: 'INVALID_AMOUNT' });
183
+ }
184
+ return { valid: true };
185
+ };
186
+ ```
187
+
188
+ `ExecutionError` messages and metadata appear in the Elevasis dashboard under the failed execution's detail view.
189
+
190
+ ### Using ToolingError
191
+
192
+ `ToolingError` is thrown by lower-level platform operations (not `platform.call()` directly). You may encounter it in advanced scenarios:
193
+
194
+ ```typescript
195
+ import { ToolingError } from '@elevasis/sdk';
196
+
197
+ const step = async (input) => {
198
+ try {
199
+ return await doSomething(input);
200
+ } catch (err) {
201
+ if (err instanceof ToolingError) {
202
+ // check err.type for the error category
203
+ console.error('Tooling error:', err.type, err.message);
204
+ }
205
+ throw err;
206
+ }
207
+ };
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Logging in Steps
213
+
214
+ The `context.logger` writes structured logs attached to the execution. Use it instead of `console.log` so logs appear in the dashboard alongside the execution record.
215
+
216
+ ```typescript
217
+ import type { StepHandler } from '@elevasis/sdk';
218
+
219
+ const processStep: StepHandler = async (input, context) => {
220
+ context.logger.info('Starting process', { userId: input.userId });
221
+
222
+ const result = await doWork(input);
223
+
224
+ context.logger.info('Process complete', { resultId: result.id });
225
+ return result;
226
+ };
227
+ ```
228
+
229
+ Avoid logging sensitive values (API keys, passwords, PII) since logs are stored and visible in the dashboard.
230
+
231
+ ---
232
+
233
+ ## Using the Execution Store
234
+
235
+ `context.store` is a simple key-value store scoped to the current execution. Use it to pass data between steps without coupling step interfaces, or to checkpoint long-running work.
236
+
237
+ ```typescript
238
+ const firstStep: StepHandler = async (input, context) => {
239
+ const data = await fetchExpensiveData(input.id);
240
+
241
+ // Save for use by later steps
242
+ await context.store.set('fetchedData', JSON.stringify(data));
243
+
244
+ return { fetched: true };
245
+ };
246
+
247
+ const secondStep: StepHandler = async (input, context) => {
248
+ const raw = await context.store.get('fetchedData');
249
+ const data = JSON.parse(raw ?? '{}');
250
+
251
+ return { processed: transform(data) };
252
+ };
253
+ ```
254
+
255
+ Store values are strings. Serialize objects with `JSON.stringify` and parse with `JSON.parse`.
256
+
257
+ ---
258
+
259
+ ## Resource Status
260
+
261
+ ### Dev vs Production
262
+
263
+ While building a resource, set `config.status` to `'dev'`:
264
+
265
+ ```typescript
266
+ const myWorkflow: WorkflowDefinition = {
267
+ config: {
268
+ name: 'my-workflow',
269
+ description: 'Does something useful',
270
+ status: 'dev', // only manually triggerable
271
+ },
272
+ // ...
273
+ };
274
+ ```
275
+
276
+ Dev resources:
277
+
278
+ - Appear in `elevasis-sdk resources` output
279
+ - Can be triggered with `elevasis-sdk exec my-workflow --input '{...}'`
280
+ - Will NOT receive scheduled or webhook-triggered executions
281
+ - Will NOT appear as available to external callers
282
+
283
+ When you are ready to go live, change to `'prod'` and redeploy:
284
+
285
+ ```typescript
286
+ config: {
287
+ name: 'my-workflow',
288
+ description: 'Does something useful',
289
+ status: 'prod', // receives all execution sources
290
+ },
291
+ ```
292
+
293
+ ### Global Default Status
294
+
295
+ Set a project-wide default in `elevasis.config.ts` to keep all resources in `'dev'` mode during development without touching each resource file:
296
+
297
+ ```typescript
298
+ import type { ElevasConfig } from '@elevasis/sdk';
299
+
300
+ const config: ElevasConfig = {
301
+ defaultStatus: 'dev',
302
+ };
303
+
304
+ export default config;
305
+ ```
306
+
307
+ Individual resources that set their own `config.status` override this default.
308
+
309
+ ---
310
+
311
+ ## Organizing Multiple Resources
312
+
313
+ As your project grows, organize resources by business domain. Each domain gets its own directory with an `index.ts` barrel that exports `workflows` and `agents` arrays:
314
+
315
+ ```typescript
316
+ // src/orders/fulfill-order.ts
317
+ export const fulfillOrder: WorkflowDefinition = { ... };
318
+
319
+ // src/billing/send-invoice.ts
320
+ export const sendInvoice: WorkflowDefinition = { ... };
321
+
322
+ // src/orders/index.ts
323
+ import { fulfillOrder } from './fulfill-order.js';
324
+ export const workflows = [fulfillOrder];
325
+ export const agents = [];
326
+
327
+ // src/billing/index.ts
328
+ import { sendInvoice } from './send-invoice.js';
329
+ export const workflows = [sendInvoice];
330
+ export const agents = [];
331
+
332
+ // src/index.ts
333
+ import type { OrganizationResources } from '@elevasis/sdk';
334
+ import * as orders from './orders/index.js';
335
+ import * as billing from './billing/index.js';
336
+
337
+ const org: OrganizationResources = {
338
+ workflows: [
339
+ ...orders.workflows,
340
+ ...billing.workflows,
341
+ ],
342
+ agents: [
343
+ ...orders.agents,
344
+ ...billing.agents,
345
+ ],
346
+ };
347
+
348
+ export default org;
349
+ ```
350
+
351
+ The keys in `workflows` and `agents` are the resource identifiers used in CLI commands and the dashboard. Choose names that are descriptive and use kebab-case for consistency.
352
+
353
+ ---
354
+
355
+ **Last Updated:** 2026-02-25