@devramps/sdk-typescript 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.
- package/LICENSE +21 -0
- package/README.md +461 -0
- package/dist/base/base-step.d.ts +41 -0
- package/dist/base/base-step.js +38 -0
- package/dist/base/polling-step.d.ts +85 -0
- package/dist/base/polling-step.js +80 -0
- package/dist/base/requires-approval-step.d.ts +5 -0
- package/dist/base/requires-approval-step.js +8 -0
- package/dist/base/simple-step.d.ts +51 -0
- package/dist/base/simple-step.js +52 -0
- package/dist/decorators/step.d.ts +24 -0
- package/dist/decorators/step.js +45 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +35 -0
- package/dist/logging/step-logger.d.ts +35 -0
- package/dist/logging/step-logger.js +94 -0
- package/dist/output/step-output.d.ts +286 -0
- package/dist/output/step-output.js +108 -0
- package/dist/registry/step-registry.d.ts +93 -0
- package/dist/registry/step-registry.js +238 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +3 -0
- package/package.json +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 DevRamps
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
# @devramps/step-sdk
|
|
2
|
+
|
|
3
|
+
SDK for building custom deployment steps for DevRamps. Create simple one-shot steps or polling steps for long-running operations, with optional approval workflows.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @devramps/step-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- Node.js >= 18.0.0
|
|
14
|
+
- TypeScript >= 5.0.0
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Simple Step
|
|
19
|
+
|
|
20
|
+
A simple step runs once and returns a result.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { SimpleStep, Step, StepOutputs, StepRegistry } from "@devramps/step-sdk";
|
|
24
|
+
import { z } from "zod";
|
|
25
|
+
|
|
26
|
+
// Define your input schema
|
|
27
|
+
const DeploySchema = z.object({
|
|
28
|
+
target: z.string(),
|
|
29
|
+
version: z.string(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
type DeployParams = z.infer<typeof DeploySchema>;
|
|
33
|
+
|
|
34
|
+
// Create your step
|
|
35
|
+
@Step({ name: "Deploy", type: "deploy", schema: DeploySchema })
|
|
36
|
+
class DeployStep extends SimpleStep<DeployParams> {
|
|
37
|
+
async run(params: DeployParams) {
|
|
38
|
+
this.logger.info("Starting deployment", { target: params.target });
|
|
39
|
+
|
|
40
|
+
// Your deployment logic here
|
|
41
|
+
const deploymentId = `deploy-${params.target}-${params.version}`;
|
|
42
|
+
|
|
43
|
+
this.logger.info("Deployment complete", { deploymentId });
|
|
44
|
+
return StepOutputs.success({ deploymentId });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Register and run
|
|
49
|
+
StepRegistry.run([DeployStep]);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Polling Step
|
|
53
|
+
|
|
54
|
+
A polling step is used for long-running operations that need status checks.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { PollingStep, Step, StepOutputs, StepRegistry } from "@devramps/step-sdk";
|
|
58
|
+
import { z } from "zod";
|
|
59
|
+
|
|
60
|
+
const BuildSchema = z.object({
|
|
61
|
+
project: z.string(),
|
|
62
|
+
branch: z.string(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
type BuildParams = z.infer<typeof BuildSchema>;
|
|
66
|
+
|
|
67
|
+
type BuildPollingState = {
|
|
68
|
+
buildId: string;
|
|
69
|
+
startedAt: number;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
@Step({ name: "Build", type: "build", schema: BuildSchema })
|
|
73
|
+
class BuildStep extends PollingStep<BuildParams, BuildPollingState> {
|
|
74
|
+
async trigger(params: BuildParams) {
|
|
75
|
+
this.logger.info("Starting build", { project: params.project });
|
|
76
|
+
|
|
77
|
+
// Start the build and return initial polling state
|
|
78
|
+
const buildId = await startBuild(params.project, params.branch);
|
|
79
|
+
|
|
80
|
+
return StepOutputs.triggered({
|
|
81
|
+
buildId,
|
|
82
|
+
startedAt: Date.now(),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async poll(params: BuildParams, state: BuildPollingState) {
|
|
87
|
+
const status = await checkBuildStatus(state.buildId);
|
|
88
|
+
|
|
89
|
+
if (status === "running") {
|
|
90
|
+
// Still running - poll again in 5 seconds
|
|
91
|
+
return StepOutputs.pollAgain(state, 5000);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (status === "failed") {
|
|
95
|
+
return StepOutputs.failed("Build failed", "BUILD_FAILED");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return StepOutputs.success({ buildId: state.buildId, status: "complete" });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
StepRegistry.run([BuildStep]);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Approval Workflows
|
|
106
|
+
|
|
107
|
+
Steps can require approval before execution by overriding the `prepare` method.
|
|
108
|
+
|
|
109
|
+
### Simple Step with Approval
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { SimpleStep, Step, StepOutputs, ApprovalContext } from "@devramps/step-sdk";
|
|
113
|
+
import { z } from "zod";
|
|
114
|
+
|
|
115
|
+
const DeleteUserSchema = z.object({
|
|
116
|
+
userId: z.string(),
|
|
117
|
+
reason: z.string(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
type DeleteUserParams = z.infer<typeof DeleteUserSchema>;
|
|
121
|
+
|
|
122
|
+
@Step({ name: "Delete User", type: "delete-user", schema: DeleteUserSchema })
|
|
123
|
+
class DeleteUserStep extends SimpleStep<DeleteUserParams> {
|
|
124
|
+
// Override prepare to require approval
|
|
125
|
+
async prepare(params: DeleteUserParams) {
|
|
126
|
+
return StepOutputs.approvalRequired({
|
|
127
|
+
message: `Delete user ${params.userId}? Reason: ${params.reason}`,
|
|
128
|
+
approvers: ["admin@example.com"],
|
|
129
|
+
metadata: { userId: params.userId },
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async run(params: DeleteUserParams, approval?: ApprovalContext) {
|
|
134
|
+
this.logger.info("Deleting user", {
|
|
135
|
+
userId: params.userId,
|
|
136
|
+
approvedBy: approval?.approverId,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await deleteUser(params.userId);
|
|
140
|
+
|
|
141
|
+
return StepOutputs.success({ deleted: true });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Polling Step with Approval
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { PollingStep, Step, StepOutputs, ApprovalContext } from "@devramps/step-sdk";
|
|
150
|
+
import { z } from "zod";
|
|
151
|
+
|
|
152
|
+
const ProductionDeploySchema = z.object({
|
|
153
|
+
service: z.string(),
|
|
154
|
+
version: z.string(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
type ProductionDeployParams = z.infer<typeof ProductionDeploySchema>;
|
|
158
|
+
|
|
159
|
+
type DeployState = {
|
|
160
|
+
deploymentId: string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
@Step({ name: "Production Deploy", type: "production-deploy", schema: ProductionDeploySchema })
|
|
164
|
+
class ProductionDeployStep extends PollingStep<ProductionDeployParams, DeployState> {
|
|
165
|
+
async prepare(params: ProductionDeployParams) {
|
|
166
|
+
return StepOutputs.approvalRequired({
|
|
167
|
+
message: `Deploy ${params.service} v${params.version} to production?`,
|
|
168
|
+
approvers: ["release-manager@example.com", "oncall@example.com"],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async trigger(params: ProductionDeployParams, approval?: ApprovalContext) {
|
|
173
|
+
this.logger.info("Starting production deployment", {
|
|
174
|
+
service: params.service,
|
|
175
|
+
approvedBy: approval?.approverId,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const deploymentId = await startProductionDeploy(params);
|
|
179
|
+
return StepOutputs.triggered({ deploymentId });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async poll(_params: ProductionDeployParams, state: DeployState) {
|
|
183
|
+
const status = await getDeploymentStatus(state.deploymentId);
|
|
184
|
+
|
|
185
|
+
if (status === "in_progress") {
|
|
186
|
+
return StepOutputs.pollAgain(state, 10000);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (status === "failed") {
|
|
190
|
+
return StepOutputs.failed("Deployment failed", "DEPLOY_FAILED");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return StepOutputs.success({ deploymentId: state.deploymentId });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## API Reference
|
|
199
|
+
|
|
200
|
+
### Step Outputs
|
|
201
|
+
|
|
202
|
+
The SDK provides helper functions for creating step outputs:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import { StepOutputs } from "@devramps/step-sdk";
|
|
206
|
+
|
|
207
|
+
// Success with optional data
|
|
208
|
+
StepOutputs.success();
|
|
209
|
+
StepOutputs.success({ key: "value" });
|
|
210
|
+
|
|
211
|
+
// Failure with error message and optional error code
|
|
212
|
+
StepOutputs.failed("Something went wrong");
|
|
213
|
+
StepOutputs.failed("Not found", "NOT_FOUND");
|
|
214
|
+
|
|
215
|
+
// Approval required (used in prepare method)
|
|
216
|
+
StepOutputs.approvalRequired({
|
|
217
|
+
message: "Please approve this action",
|
|
218
|
+
approvers: ["admin@example.com"], // optional
|
|
219
|
+
metadata: { key: "value" }, // optional
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Triggered with polling state (used in PollingStep.trigger)
|
|
223
|
+
StepOutputs.triggered({ jobId: "123", startedAt: Date.now() });
|
|
224
|
+
|
|
225
|
+
// Poll again with updated state and optional delay (used in PollingStep.poll)
|
|
226
|
+
StepOutputs.pollAgain({ jobId: "123", attempt: 2 }, 5000);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### @Step Decorator
|
|
230
|
+
|
|
231
|
+
The `@Step` decorator adds metadata to your step class:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
@Step({
|
|
235
|
+
name: "Human-readable name",
|
|
236
|
+
type: "unique-step-type",
|
|
237
|
+
schema: zodSchema,
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
- `name`: Display name for the step
|
|
242
|
+
- `type`: Unique identifier used when executing the step
|
|
243
|
+
- `schema`: Zod schema for validating input parameters
|
|
244
|
+
|
|
245
|
+
### StepRegistry
|
|
246
|
+
|
|
247
|
+
The registry handles CLI argument parsing and step execution:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { StepRegistry } from "@devramps/step-sdk";
|
|
251
|
+
|
|
252
|
+
// Register all your steps
|
|
253
|
+
StepRegistry.run([
|
|
254
|
+
DeployStep,
|
|
255
|
+
BuildStep,
|
|
256
|
+
DeleteUserStep,
|
|
257
|
+
]);
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### CLI Arguments
|
|
261
|
+
|
|
262
|
+
When running your step entrypoint:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
node entrypoint.js --input '{"job":"EXECUTE","type":"deploy","params":{"target":"staging","version":"1.0.0"}}'
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
| Argument | Description | Default |
|
|
269
|
+
|----------|-------------|---------|
|
|
270
|
+
| `--input` | JSON input with job type and parameters | Required |
|
|
271
|
+
| `--output` | Path to write output JSON | `/tmp/step-output.json` |
|
|
272
|
+
| `--log-dir` | Directory for log files | `/tmp/step-logs` |
|
|
273
|
+
| `--execution-id` | Unique ID for this execution | Auto-generated |
|
|
274
|
+
|
|
275
|
+
#### Input Format
|
|
276
|
+
|
|
277
|
+
**Synthesize Metadata** - Get metadata for all registered steps:
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"job": "SYNTHESIZE-METADATA"
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Execute Step** - Run a specific step:
|
|
285
|
+
```json
|
|
286
|
+
{
|
|
287
|
+
"job": "EXECUTE",
|
|
288
|
+
"type": "deploy",
|
|
289
|
+
"params": { "target": "staging", "version": "1.0.0" }
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Execute with Approval Context**:
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"job": "EXECUTE",
|
|
297
|
+
"type": "delete-user",
|
|
298
|
+
"params": { "userId": "123", "reason": "requested" },
|
|
299
|
+
"approvalContext": {
|
|
300
|
+
"approved": true,
|
|
301
|
+
"approverId": "admin@example.com"
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Execute Poll Phase**:
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"job": "EXECUTE",
|
|
310
|
+
"type": "build",
|
|
311
|
+
"params": { "project": "my-app", "branch": "main" },
|
|
312
|
+
"pollingState": {
|
|
313
|
+
"buildId": "build-123",
|
|
314
|
+
"startedAt": 1704067200000
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Logging
|
|
320
|
+
|
|
321
|
+
Steps have access to a structured logger:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
class MyStep extends SimpleStep<MyParams> {
|
|
325
|
+
async run(params: MyParams) {
|
|
326
|
+
// Log info with optional data
|
|
327
|
+
this.logger.info("Processing started", { itemCount: 10 });
|
|
328
|
+
|
|
329
|
+
// Log errors
|
|
330
|
+
this.logger.error("Failed to connect", { host: "example.com" });
|
|
331
|
+
|
|
332
|
+
return StepOutputs.success();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Logs are written as JSON lines to `{log-dir}/{execution-id}.jsonl`:
|
|
338
|
+
|
|
339
|
+
```json
|
|
340
|
+
{"timestamp":"2024-01-01T12:00:00.000Z","level":"info","message":"Processing started","data":{"itemCount":10},"stepType":"my-step","executionId":"exec-123"}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Output Types
|
|
344
|
+
|
|
345
|
+
### RunOutput (SimpleStep.run)
|
|
346
|
+
- `SUCCESS` with optional data
|
|
347
|
+
- `FAILED` with error message
|
|
348
|
+
|
|
349
|
+
### TriggerOutput (PollingStep.trigger)
|
|
350
|
+
- `TRIGGERED` with polling state
|
|
351
|
+
- `FAILED` with error message
|
|
352
|
+
|
|
353
|
+
### PollOutput (PollingStep.poll)
|
|
354
|
+
- `SUCCESS` with optional data
|
|
355
|
+
- `POLL_AGAIN` with updated polling state and optional retry delay
|
|
356
|
+
- `FAILED` with error message
|
|
357
|
+
|
|
358
|
+
### PrepareOutput (approval flow)
|
|
359
|
+
- `APPROVAL_REQUIRED` with approval request details
|
|
360
|
+
- `FAILED` with error message
|
|
361
|
+
|
|
362
|
+
## Complete Example
|
|
363
|
+
|
|
364
|
+
Here's a complete example with multiple step types:
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
// steps.ts
|
|
368
|
+
import {
|
|
369
|
+
SimpleStep,
|
|
370
|
+
PollingStep,
|
|
371
|
+
Step,
|
|
372
|
+
StepOutputs,
|
|
373
|
+
StepRegistry,
|
|
374
|
+
ApprovalContext,
|
|
375
|
+
} from "@devramps/step-sdk";
|
|
376
|
+
import { z } from "zod";
|
|
377
|
+
|
|
378
|
+
// Schema definitions
|
|
379
|
+
const NotifySchema = z.object({
|
|
380
|
+
channel: z.string(),
|
|
381
|
+
message: z.string(),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const MigrationSchema = z.object({
|
|
385
|
+
database: z.string(),
|
|
386
|
+
version: z.string(),
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Simple notification step
|
|
390
|
+
@Step({ name: "Send Notification", type: "notify", schema: NotifySchema })
|
|
391
|
+
class NotifyStep extends SimpleStep<z.infer<typeof NotifySchema>> {
|
|
392
|
+
async run(params: z.infer<typeof NotifySchema>) {
|
|
393
|
+
this.logger.info("Sending notification", { channel: params.channel });
|
|
394
|
+
// Send notification logic...
|
|
395
|
+
return StepOutputs.success({ sent: true });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Database migration with approval and polling
|
|
400
|
+
type MigrationState = {
|
|
401
|
+
migrationId: string;
|
|
402
|
+
startedAt: number;
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
@Step({ name: "Database Migration", type: "db-migration", schema: MigrationSchema })
|
|
406
|
+
class MigrationStep extends PollingStep<z.infer<typeof MigrationSchema>, MigrationState> {
|
|
407
|
+
async prepare(params: z.infer<typeof MigrationSchema>) {
|
|
408
|
+
return StepOutputs.approvalRequired({
|
|
409
|
+
message: `Run migration ${params.version} on ${params.database}?`,
|
|
410
|
+
approvers: ["dba@example.com"],
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async trigger(params: z.infer<typeof MigrationSchema>, approval?: ApprovalContext) {
|
|
415
|
+
this.logger.info("Starting migration", {
|
|
416
|
+
database: params.database,
|
|
417
|
+
approvedBy: approval?.approverId,
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const migrationId = `migration-${Date.now()}`;
|
|
421
|
+
return StepOutputs.triggered({
|
|
422
|
+
migrationId,
|
|
423
|
+
startedAt: Date.now(),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async poll(_params: z.infer<typeof MigrationSchema>, state: MigrationState) {
|
|
428
|
+
// Check migration status...
|
|
429
|
+
const elapsed = Date.now() - state.startedAt;
|
|
430
|
+
|
|
431
|
+
if (elapsed < 30000) {
|
|
432
|
+
return StepOutputs.pollAgain(state, 5000);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return StepOutputs.success({
|
|
436
|
+
migrationId: state.migrationId,
|
|
437
|
+
duration: elapsed,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Run the registry
|
|
443
|
+
StepRegistry.run([NotifyStep, MigrationStep]);
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## TypeScript Configuration
|
|
447
|
+
|
|
448
|
+
Ensure your `tsconfig.json` includes decorator support:
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"compilerOptions": {
|
|
453
|
+
"experimentalDecorators": true,
|
|
454
|
+
"emitDecoratorMetadata": true
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
## License
|
|
460
|
+
|
|
461
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ZodType, z } from "zod";
|
|
2
|
+
import type { StepLogger } from "../logging/step-logger";
|
|
3
|
+
import type { PrepareOutput } from "../output/step-output";
|
|
4
|
+
export type StepKind = "simple" | "polling";
|
|
5
|
+
export interface StepMetadata<S extends ZodType = ZodType> {
|
|
6
|
+
name: string;
|
|
7
|
+
type: string;
|
|
8
|
+
schema: S;
|
|
9
|
+
stepKind: StepKind;
|
|
10
|
+
requiresApproval: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Base class for all steps.
|
|
14
|
+
* Provides logging and common functionality.
|
|
15
|
+
*/
|
|
16
|
+
export declare abstract class BaseStep<TParams = unknown> {
|
|
17
|
+
protected logger: StepLogger;
|
|
18
|
+
/**
|
|
19
|
+
* Called by the registry to inject the logger before execution.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
_setLogger(logger: StepLogger): void;
|
|
23
|
+
/**
|
|
24
|
+
* Returns metadata about this step.
|
|
25
|
+
* Implemented by the @Step decorator.
|
|
26
|
+
*/
|
|
27
|
+
getMetadata(): StepMetadata;
|
|
28
|
+
/**
|
|
29
|
+
* Optional prepare method for approval flow.
|
|
30
|
+
* Override this to require approval before execution.
|
|
31
|
+
* If not overridden, the step will not require approval.
|
|
32
|
+
*/
|
|
33
|
+
prepare(_params: TParams): Promise<PrepareOutput>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Type representing a class constructor that produces a BaseStep instance
|
|
37
|
+
* with the getMetadata method available.
|
|
38
|
+
*/
|
|
39
|
+
export type StepClass<S extends ZodType = ZodType> = new (...args: any[]) => BaseStep<z.infer<S>> & {
|
|
40
|
+
getMetadata(): StepMetadata<S>;
|
|
41
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BaseStep = void 0;
|
|
4
|
+
const step_logger_1 = require("../logging/step-logger");
|
|
5
|
+
const step_output_1 = require("../output/step-output");
|
|
6
|
+
/**
|
|
7
|
+
* Base class for all steps.
|
|
8
|
+
* Provides logging and common functionality.
|
|
9
|
+
*/
|
|
10
|
+
class BaseStep {
|
|
11
|
+
logger = new step_logger_1.NoOpLogger();
|
|
12
|
+
/**
|
|
13
|
+
* Called by the registry to inject the logger before execution.
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
_setLogger(logger) {
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns metadata about this step.
|
|
21
|
+
* Implemented by the @Step decorator.
|
|
22
|
+
*/
|
|
23
|
+
getMetadata() {
|
|
24
|
+
throw new Error("getMetadata not implemented. Did you forget to add the @Step decorator?");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Optional prepare method for approval flow.
|
|
28
|
+
* Override this to require approval before execution.
|
|
29
|
+
* If not overridden, the step will not require approval.
|
|
30
|
+
*/
|
|
31
|
+
async prepare(_params) {
|
|
32
|
+
// Default: no approval required - this should never be called
|
|
33
|
+
// The registry checks if prepare is overridden before calling it
|
|
34
|
+
return step_output_1.StepOutputs.failed("prepare() called but not implemented", "PREPARE_NOT_IMPLEMENTED");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.BaseStep = BaseStep;
|
|
38
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmFzZS1zdGVwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2Jhc2UvYmFzZS1zdGVwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBLHdEQUFvRDtBQUVwRCx1REFBb0Q7QUFZcEQ7OztHQUdHO0FBQ0gsTUFBc0IsUUFBUTtJQUNsQixNQUFNLEdBQWUsSUFBSSx3QkFBVSxFQUFFLENBQUM7SUFFaEQ7OztPQUdHO0lBQ0gsVUFBVSxDQUFDLE1BQWtCO1FBQzNCLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO0lBQ3ZCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxXQUFXO1FBQ1QsTUFBTSxJQUFJLEtBQUssQ0FDYix5RUFBeUUsQ0FDMUUsQ0FBQztJQUNKLENBQUM7SUFFRDs7OztPQUlHO0lBQ0gsS0FBSyxDQUFDLE9BQU8sQ0FBQyxPQUFnQjtRQUM1Qiw4REFBOEQ7UUFDOUQsaUVBQWlFO1FBQ2pFLE9BQU8seUJBQVcsQ0FBQyxNQUFNLENBQ3ZCLHNDQUFzQyxFQUN0Qyx5QkFBeUIsQ0FDMUIsQ0FBQztJQUNKLENBQUM7Q0FDRjtBQWxDRCw0QkFrQ0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IFpvZFR5cGUsIHogfSBmcm9tIFwiem9kXCI7XG5pbXBvcnQgdHlwZSB7IFN0ZXBMb2dnZXJ9IGZyb20gXCIuLi9sb2dnaW5nL3N0ZXAtbG9nZ2VyXCI7XG5pbXBvcnQgeyBOb09wTG9nZ2VyIH0gZnJvbSBcIi4uL2xvZ2dpbmcvc3RlcC1sb2dnZXJcIjtcbmltcG9ydCB0eXBlIHsgUHJlcGFyZU91dHB1dH0gZnJvbSBcIi4uL291dHB1dC9zdGVwLW91dHB1dFwiO1xuaW1wb3J0IHsgU3RlcE91dHB1dHMgfSBmcm9tIFwiLi4vb3V0cHV0L3N0ZXAtb3V0cHV0XCI7XG5cbmV4cG9ydCB0eXBlIFN0ZXBLaW5kID0gXCJzaW1wbGVcIiB8IFwicG9sbGluZ1wiO1xuXG5leHBvcnQgaW50ZXJmYWNlIFN0ZXBNZXRhZGF0YTxTIGV4dGVuZHMgWm9kVHlwZSA9IFpvZFR5cGU+IHtcbiAgbmFtZTogc3RyaW5nO1xuICB0eXBlOiBzdHJpbmc7XG4gIHNjaGVtYTogUztcbiAgc3RlcEtpbmQ6IFN0ZXBLaW5kO1xuICByZXF1aXJlc0FwcHJvdmFsOiBib29sZWFuO1xufVxuXG4vKipcbiAqIEJhc2UgY2xhc3MgZm9yIGFsbCBzdGVwcy5cbiAqIFByb3ZpZGVzIGxvZ2dpbmcgYW5kIGNvbW1vbiBmdW5jdGlvbmFsaXR5LlxuICovXG5leHBvcnQgYWJzdHJhY3QgY2xhc3MgQmFzZVN0ZXA8VFBhcmFtcyA9IHVua25vd24+IHtcbiAgcHJvdGVjdGVkIGxvZ2dlcjogU3RlcExvZ2dlciA9IG5ldyBOb09wTG9nZ2VyKCk7XG5cbiAgLyoqXG4gICAqIENhbGxlZCBieSB0aGUgcmVnaXN0cnkgdG8gaW5qZWN0IHRoZSBsb2dnZXIgYmVmb3JlIGV4ZWN1dGlvbi5cbiAgICogQGludGVybmFsXG4gICAqL1xuICBfc2V0TG9nZ2VyKGxvZ2dlcjogU3RlcExvZ2dlcik6IHZvaWQge1xuICAgIHRoaXMubG9nZ2VyID0gbG9nZ2VyO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgbWV0YWRhdGEgYWJvdXQgdGhpcyBzdGVwLlxuICAgKiBJbXBsZW1lbnRlZCBieSB0aGUgQFN0ZXAgZGVjb3JhdG9yLlxuICAgKi9cbiAgZ2V0TWV0YWRhdGEoKTogU3RlcE1ldGFkYXRhIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBcImdldE1ldGFkYXRhIG5vdCBpbXBsZW1lbnRlZC4gRGlkIHlvdSBmb3JnZXQgdG8gYWRkIHRoZSBAU3RlcCBkZWNvcmF0b3I/XCJcbiAgICApO1xuICB9XG5cbiAgLyoqXG4gICAqIE9wdGlvbmFsIHByZXBhcmUgbWV0aG9kIGZvciBhcHByb3ZhbCBmbG93LlxuICAgKiBPdmVycmlkZSB0aGlzIHRvIHJlcXVpcmUgYXBwcm92YWwgYmVmb3JlIGV4ZWN1dGlvbi5cbiAgICogSWYgbm90IG92ZXJyaWRkZW4sIHRoZSBzdGVwIHdpbGwgbm90IHJlcXVpcmUgYXBwcm92YWwuXG4gICAqL1xuICBhc3luYyBwcmVwYXJlKF9wYXJhbXM6IFRQYXJhbXMpOiBQcm9taXNlPFByZXBhcmVPdXRwdXQ+IHtcbiAgICAvLyBEZWZhdWx0OiBubyBhcHByb3ZhbCByZXF1aXJlZCAtIHRoaXMgc2hvdWxkIG5ldmVyIGJlIGNhbGxlZFxuICAgIC8vIFRoZSByZWdpc3RyeSBjaGVja3MgaWYgcHJlcGFyZSBpcyBvdmVycmlkZGVuIGJlZm9yZSBjYWxsaW5nIGl0XG4gICAgcmV0dXJuIFN0ZXBPdXRwdXRzLmZhaWxlZChcbiAgICAgIFwicHJlcGFyZSgpIGNhbGxlZCBidXQgbm90IGltcGxlbWVudGVkXCIsXG4gICAgICBcIlBSRVBBUkVfTk9UX0lNUExFTUVOVEVEXCJcbiAgICApO1xuICB9XG59XG5cbi8qKlxuICogVHlwZSByZXByZXNlbnRpbmcgYSBjbGFzcyBjb25zdHJ1Y3RvciB0aGF0IHByb2R1Y2VzIGEgQmFzZVN0ZXAgaW5zdGFuY2VcbiAqIHdpdGggdGhlIGdldE1ldGFkYXRhIG1ldGhvZCBhdmFpbGFibGUuXG4gKi9cbmV4cG9ydCB0eXBlIFN0ZXBDbGFzczxTIGV4dGVuZHMgWm9kVHlwZSA9IFpvZFR5cGU+ID0gbmV3IChcbiAgLi4uYXJnczogYW55W11cbikgPT4gQmFzZVN0ZXA8ei5pbmZlcjxTPj4gJiB7XG4gIGdldE1ldGFkYXRhKCk6IFN0ZXBNZXRhZGF0YTxTPjtcbn07XG4iXX0=
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { StepMetadata } from "./base-step";
|
|
2
|
+
import { BaseStep } from "./base-step";
|
|
3
|
+
import type { ApprovalContext, PollOutput, StepOutput, TriggerOutput } from "../output/step-output";
|
|
4
|
+
/**
|
|
5
|
+
* A polling step for long-running operations that need status checks.
|
|
6
|
+
*
|
|
7
|
+
* Users extend this class and implement:
|
|
8
|
+
* - `trigger(params)` - Start the operation, return initial polling state
|
|
9
|
+
* - `poll(params, pollingState)` - Check status, return POLL_AGAIN, SUCCESS, or FAILED
|
|
10
|
+
*
|
|
11
|
+
* Optionally override `prepare` to require approval before triggering.
|
|
12
|
+
*
|
|
13
|
+
* @typeParam TParams - The input parameters type (validated by schema)
|
|
14
|
+
* @typeParam TPollingState - The polling state type passed between poll calls
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* type JobParams = { target: string };
|
|
19
|
+
* type JobPollingState = { jobId: string; startedAt: number };
|
|
20
|
+
*
|
|
21
|
+
* @Step({ name: "Long Job", type: "long-job", schema: jobSchema })
|
|
22
|
+
* class LongJobStep extends PollingStep<JobParams, JobPollingState> {
|
|
23
|
+
* async trigger(params: JobParams): Promise<TriggerOutput<JobPollingState>> {
|
|
24
|
+
* const jobId = await startJob(params);
|
|
25
|
+
* return StepOutputs.triggered({ jobId, startedAt: Date.now() });
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* async poll(params: JobParams, state: JobPollingState): Promise<PollOutput<JobPollingState>> {
|
|
29
|
+
* const status = await checkJob(state.jobId);
|
|
30
|
+
* if (status === "running") {
|
|
31
|
+
* return StepOutputs.pollAgain(state, 5000);
|
|
32
|
+
* }
|
|
33
|
+
* return StepOutputs.success({ result: status });
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example With approval
|
|
39
|
+
* ```typescript
|
|
40
|
+
* @Step({ name: "Dangerous Job", type: "dangerous-job", schema: dangerousSchema })
|
|
41
|
+
* class DangerousJobStep extends PollingStep<DangerousParams, DangerousPollingState> {
|
|
42
|
+
* async prepare(params: DangerousParams): Promise<PrepareOutput> {
|
|
43
|
+
* return StepOutputs.approvalRequired({
|
|
44
|
+
* message: `Run dangerous job on ${params.target}?`,
|
|
45
|
+
* });
|
|
46
|
+
* }
|
|
47
|
+
*
|
|
48
|
+
* async trigger(params: DangerousParams, approval: ApprovalContext): Promise<TriggerOutput<DangerousPollingState>> {
|
|
49
|
+
* const jobId = await startDangerousJob(params);
|
|
50
|
+
* return StepOutputs.triggered({ jobId });
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* async poll(params: DangerousParams, state: DangerousPollingState): Promise<PollOutput<DangerousPollingState>> {
|
|
54
|
+
* // ... check status
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare abstract class PollingStep<TParams, TPollingState extends Record<string, unknown> = Record<string, unknown>> extends BaseStep<TParams> {
|
|
60
|
+
/**
|
|
61
|
+
* Start the long-running operation.
|
|
62
|
+
* @param params - The validated input parameters
|
|
63
|
+
* @param approval - Approval context if this step requires approval (has prepare method)
|
|
64
|
+
* @returns TRIGGERED with initial polling state, or FAILED
|
|
65
|
+
*/
|
|
66
|
+
abstract trigger(params: TParams, approval?: ApprovalContext): Promise<TriggerOutput<TPollingState>>;
|
|
67
|
+
/**
|
|
68
|
+
* Check the status of the operation.
|
|
69
|
+
* @param params - The original input parameters
|
|
70
|
+
* @param pollingState - State from previous trigger() or poll() call
|
|
71
|
+
* @returns POLL_AGAIN with updated state, SUCCESS, or FAILED
|
|
72
|
+
*/
|
|
73
|
+
abstract poll(params: TParams, pollingState: TPollingState): Promise<PollOutput<TPollingState>>;
|
|
74
|
+
/**
|
|
75
|
+
* Called by the registry to execute the trigger phase.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
executeTrigger(params: TParams, approval?: ApprovalContext): Promise<StepOutput>;
|
|
79
|
+
/**
|
|
80
|
+
* Called by the registry to execute the poll phase.
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
executePoll(params: TParams, pollingState: TPollingState): Promise<StepOutput>;
|
|
84
|
+
getMetadata(): StepMetadata;
|
|
85
|
+
}
|