@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 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
+ }