@steadlake/run 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/adapters/generic.d.ts +3 -0
- package/dist/adapters/generic.d.ts.map +1 -0
- package/dist/adapters/generic.js +5 -0
- package/dist/adapters/next.d.ts +5 -0
- package/dist/adapters/next.d.ts.map +1 -0
- package/dist/adapters/next.js +6 -0
- package/dist/adapters/register.d.ts +3 -0
- package/dist/adapters/register.d.ts.map +1 -0
- package/dist/adapters/register.js +18 -0
- package/dist/client.d.ts +21 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +94 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +12 -0
- package/dist/handler.d.ts +3 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +61 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/step.d.ts +7 -0
- package/dist/step.d.ts.map +1 -0
- package/dist/step.js +42 -0
- package/dist/task.d.ts +4 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +71 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# steppler
|
|
2
|
+
|
|
3
|
+
To install dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run index.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generic.d.ts","sourceRoot":"","sources":["../../src/adapters/generic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,uCAG7C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"next.d.ts","sourceRoot":"","sources":["../../src/adapters/next.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAG7C,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,QAAQ;gBAG5B,OAAO;EAC5B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/adapters/register.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,SAAI,iBAoBrE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function registerWithRetry(client, attempts = 3) {
|
|
2
|
+
for (let i = 1; i <= attempts; i++) {
|
|
3
|
+
try {
|
|
4
|
+
await client._autoRegister();
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
catch (err) {
|
|
8
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9
|
+
if (i === attempts) {
|
|
10
|
+
console.error(`[steppler] Auto-registration failed after ${attempts} attempts: ${msg}. Tasks will not be dispatched by DEW until registration succeeds.`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const delay = 1000 * 2 ** (i - 1);
|
|
14
|
+
console.warn(`[steppler] Auto-registration attempt ${i} failed, retrying in ${delay}ms: ${msg}`);
|
|
15
|
+
await new Promise((res) => setTimeout(res, delay));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Steppler as StepplerClient } from "@steppler/api";
|
|
2
|
+
import type { Task, TaskConfig } from "../src/types.js";
|
|
3
|
+
export declare class Steppler {
|
|
4
|
+
_registry: Map<string, Task<unknown, unknown>>;
|
|
5
|
+
_client: StepplerClient;
|
|
6
|
+
_signingSecret?: string;
|
|
7
|
+
_projectId: string;
|
|
8
|
+
_registrationLock: Promise<void> | null;
|
|
9
|
+
_registrationComplete: boolean;
|
|
10
|
+
constructor(config?: {
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
apiUrl?: string;
|
|
13
|
+
signingSecret?: string;
|
|
14
|
+
projectId?: string;
|
|
15
|
+
});
|
|
16
|
+
task<TPayload = unknown, TResult = unknown>(config: TaskConfig<TPayload, TResult>): Task<TPayload, TResult>;
|
|
17
|
+
_autoRegister(): Promise<void>;
|
|
18
|
+
init(): Promise<void>;
|
|
19
|
+
_createHandler(): (req: Request) => Promise<Response>;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,eAAe,CAAC;AAG3D,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAOnD,qBAAa,QAAQ;IACpB,SAAS,sCAA6C;IACtD,OAAO,EAAE,cAAc,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAQ;IAC/C,qBAAqB,UAAS;gBAElB,MAAM,CAAC,EAAE;QACpB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,CAAC;KACnB;IA2BD,IAAI,CAAC,QAAQ,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EACzC,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,GACnC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC;IAMpB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA2D9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,cAAc;CAGd"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Steppler as StepplerClient } from "@steppler/api";
|
|
2
|
+
import { createHandler } from "./handler.js";
|
|
3
|
+
import { createTask } from "./task.js";
|
|
4
|
+
export class Steppler {
|
|
5
|
+
_registry = new Map();
|
|
6
|
+
_client;
|
|
7
|
+
_signingSecret;
|
|
8
|
+
_projectId;
|
|
9
|
+
_registrationLock = null;
|
|
10
|
+
_registrationComplete = false;
|
|
11
|
+
constructor(config) {
|
|
12
|
+
const apiKey = config?.apiKey ?? process.env.STEPPLER_API_KEY ?? "";
|
|
13
|
+
const apiUrl = config?.apiUrl ?? process.env.STEPPLER_API_URL;
|
|
14
|
+
const signingSecret = config?.signingSecret ?? process.env.STEPPLER_SIGNING_SECRET;
|
|
15
|
+
const projectId = config?.projectId ?? process.env.STEPPLER_PROJECT_ID ?? "";
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
console.warn("[steppler] No API key set — provide apiKey or set STEPPLER_API_KEY");
|
|
18
|
+
}
|
|
19
|
+
if (!projectId) {
|
|
20
|
+
console.warn("[steppler] No project ID set — provide projectId or set STEPPLER_PROJECT_ID");
|
|
21
|
+
}
|
|
22
|
+
if (!signingSecret) {
|
|
23
|
+
console.warn("[steppler] No signing secret set — requests from DEW will not be verified. Set STEPPLER_SIGNING_SECRET in production.");
|
|
24
|
+
}
|
|
25
|
+
this._signingSecret = signingSecret;
|
|
26
|
+
this._projectId = projectId;
|
|
27
|
+
this._client = new StepplerClient({
|
|
28
|
+
bearerAuth: apiKey,
|
|
29
|
+
...(apiUrl ? { serverURL: apiUrl } : {}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
task(config) {
|
|
33
|
+
const t = createTask(config, this._client);
|
|
34
|
+
this._registry.set(config.id, t);
|
|
35
|
+
return t;
|
|
36
|
+
}
|
|
37
|
+
async _autoRegister() {
|
|
38
|
+
if (!this._projectId) {
|
|
39
|
+
console.warn("[steppler] No projectId set — skipping auto-registration");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (this._registrationLock) {
|
|
43
|
+
await this._registrationLock;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
this._registrationLock = (async () => {
|
|
47
|
+
if (this._registrationComplete)
|
|
48
|
+
return;
|
|
49
|
+
const triggers = Array.from(this._registry.values()).map((task) => {
|
|
50
|
+
const triggerType = task._config.cron
|
|
51
|
+
? "cron"
|
|
52
|
+
: task._config.type === "event"
|
|
53
|
+
? "event"
|
|
54
|
+
: "manual";
|
|
55
|
+
return {
|
|
56
|
+
handler: task._config.id,
|
|
57
|
+
name: task._config.name ?? task._config.id,
|
|
58
|
+
type: triggerType,
|
|
59
|
+
config: {
|
|
60
|
+
...(task._config.queue ? { queue: task._config.queue } : {}),
|
|
61
|
+
...(task._config.retry
|
|
62
|
+
? {
|
|
63
|
+
retry: {
|
|
64
|
+
...task._config.retry,
|
|
65
|
+
backoff: task._config.retry.backoff ?? "exponential",
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
: {}),
|
|
69
|
+
...(task._config.cron
|
|
70
|
+
? {
|
|
71
|
+
cron: typeof task._config.cron === "string"
|
|
72
|
+
? { pattern: task._config.cron }
|
|
73
|
+
: task._config.cron,
|
|
74
|
+
}
|
|
75
|
+
: {}),
|
|
76
|
+
...(task._config.timeout ? { timeout: task._config.timeout } : {}),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
await this._client.triggers.register({
|
|
81
|
+
projectId: this._projectId,
|
|
82
|
+
triggers,
|
|
83
|
+
});
|
|
84
|
+
this._registrationComplete = true;
|
|
85
|
+
})();
|
|
86
|
+
await this._registrationLock;
|
|
87
|
+
}
|
|
88
|
+
async init() {
|
|
89
|
+
await this._autoRegister();
|
|
90
|
+
}
|
|
91
|
+
_createHandler() {
|
|
92
|
+
return createHandler(this._registry, this._signingSecret);
|
|
93
|
+
}
|
|
94
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAW,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI3B;AAED,qBAAa,aAAc,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI3B"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,wBAAgB,aAAa,CAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,EAC7C,aAAa,CAAC,EAAE,MAAM,IAEO,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CA6E7D"}
|
package/dist/handler.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
2
|
+
import { FatalError } from "./errors.js";
|
|
3
|
+
import { createStepHelpers } from "./step.js";
|
|
4
|
+
export function createHandler(registry, signingSecret) {
|
|
5
|
+
return async function handle(req) {
|
|
6
|
+
const body = await req.text();
|
|
7
|
+
if (signingSecret) {
|
|
8
|
+
const signature = req.headers.get("X-Steppler-Signature");
|
|
9
|
+
const expected = `sha256=${sign(body, signingSecret)}`;
|
|
10
|
+
if (signature !== expected) {
|
|
11
|
+
return new Response("Unauthorized", { status: 401 });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = JSON.parse(body);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return new Response("Invalid JSON body", { status: 400 });
|
|
20
|
+
}
|
|
21
|
+
const { runId, handler, payload, attempt = 1, state = {} } = parsed;
|
|
22
|
+
const task = registry.get(handler);
|
|
23
|
+
if (!task) {
|
|
24
|
+
return new Response(`No handler registered for "${handler}"`, {
|
|
25
|
+
status: 404,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (task._config.schema) {
|
|
29
|
+
const result = task._config.schema.safeParse(payload);
|
|
30
|
+
if (!result.success) {
|
|
31
|
+
return new Response(JSON.stringify({
|
|
32
|
+
error: "Payload validation failed",
|
|
33
|
+
issues: result.error.issues,
|
|
34
|
+
}), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const pendingEvents = [];
|
|
38
|
+
const step = createStepHelpers(state, pendingEvents);
|
|
39
|
+
try {
|
|
40
|
+
const result = await task._config.run({ payload, runId, attempt, step });
|
|
41
|
+
return Response.json({
|
|
42
|
+
result,
|
|
43
|
+
events: pendingEvents,
|
|
44
|
+
}, { status: 200 });
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (typeof err === "object" && err !== null && "__steppler_suspend" in err) {
|
|
48
|
+
const suspension = err;
|
|
49
|
+
return Response.json({ suspend: true, resumeAt: suspension.resumeAt, events: pendingEvents }, { status: 202 });
|
|
50
|
+
}
|
|
51
|
+
if (err instanceof FatalError) {
|
|
52
|
+
return Response.json({ error: err.message, runId, fatal: true }, { status: 400 });
|
|
53
|
+
}
|
|
54
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
+
return Response.json({ error: message, runId, fatal: false }, { status: 500 });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function sign(body, secret) {
|
|
60
|
+
return createHmac("sha256", secret).update(body).digest("hex");
|
|
61
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createHandler } from "../src/adapters/generic.js";
|
|
2
|
+
export { createNextHandler } from "../src/adapters/next.js";
|
|
3
|
+
export { Steppler } from "../src/client.js";
|
|
4
|
+
export { FatalError } from "../src/errors.js";
|
|
5
|
+
export type { Run, RunContext, RunResult, StepHelpers, Task, TaskConfig, TriggerOptions, } from "../src/types.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EACX,GAAG,EACH,UAAU,EACV,SAAS,EACT,WAAW,EACX,IAAI,EACJ,UAAU,EACV,cAAc,GACd,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
package/dist/step.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StepHelpers } from "./types.js";
|
|
2
|
+
export type PendingEvent = {
|
|
3
|
+
type: string;
|
|
4
|
+
metadata: Record<string, unknown>;
|
|
5
|
+
};
|
|
6
|
+
export declare function createStepHelpers(state: Record<string, unknown>, pendingEvents: PendingEvent[]): StepHelpers;
|
|
7
|
+
//# sourceMappingURL=step.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step.d.ts","sourceRoot":"","sources":["../src/step.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,YAAY,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC;AAEF,wBAAgB,iBAAiB,CAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,aAAa,EAAE,YAAY,EAAE,GAC3B,WAAW,CA+Bb"}
|
package/dist/step.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function createStepHelpers(state, pendingEvents) {
|
|
2
|
+
return {
|
|
3
|
+
async run(id, fn) {
|
|
4
|
+
if (id in state) {
|
|
5
|
+
return state[id];
|
|
6
|
+
}
|
|
7
|
+
pendingEvents.push({ type: "step_started", metadata: { step: id } });
|
|
8
|
+
const output = await fn();
|
|
9
|
+
pendingEvents.push({
|
|
10
|
+
type: "step_completed",
|
|
11
|
+
metadata: { step: id, output },
|
|
12
|
+
});
|
|
13
|
+
return output;
|
|
14
|
+
},
|
|
15
|
+
async sleep(id, duration) {
|
|
16
|
+
if (id in state)
|
|
17
|
+
return;
|
|
18
|
+
const ms = parseDuration(duration);
|
|
19
|
+
const resumeAt = new Date(Date.now() + ms).toISOString();
|
|
20
|
+
pendingEvents.push({
|
|
21
|
+
type: "step_completed",
|
|
22
|
+
metadata: { step: id, output: null },
|
|
23
|
+
});
|
|
24
|
+
throw { __steppler_suspend: true, resumeAt, step: id };
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function parseDuration(duration) {
|
|
29
|
+
const units = {
|
|
30
|
+
ms: 1,
|
|
31
|
+
s: 1000,
|
|
32
|
+
m: 60_000,
|
|
33
|
+
h: 3_600_000,
|
|
34
|
+
d: 86_400_000,
|
|
35
|
+
};
|
|
36
|
+
const match = duration.match(/^(\d+)(ms|s|m|h|d)$/);
|
|
37
|
+
if (!match)
|
|
38
|
+
throw new Error(`Invalid duration format: "${duration}". Use e.g. "30s", "5m", "2h"`);
|
|
39
|
+
const value = parseInt(match[1], 10);
|
|
40
|
+
const unit = match[2];
|
|
41
|
+
return value * units[unit];
|
|
42
|
+
}
|
package/dist/task.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Steppler as StepplerClient } from "@steppler/api";
|
|
2
|
+
import type { Task, TaskConfig } from "./types.js";
|
|
3
|
+
export declare function createTask<TPayload, TResult>(config: TaskConfig<TPayload, TResult>, client: StepplerClient): Task<TPayload, TResult>;
|
|
4
|
+
//# sourceMappingURL=task.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,IAAI,cAAc,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,KAAK,EAAkB,IAAI,EAAE,UAAU,EAAkB,MAAM,YAAY,CAAC;AAOnF,wBAAgB,UAAU,CAAC,QAAQ,EAAE,OAAO,EAC3C,MAAM,EAAE,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,EACrC,MAAM,EAAE,cAAc,GACpB,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CA6EzB"}
|
package/dist/task.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { StepplerError } from "./errors.js";
|
|
2
|
+
function sleep(ms) {
|
|
3
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
4
|
+
}
|
|
5
|
+
export function createTask(config, client) {
|
|
6
|
+
return {
|
|
7
|
+
id: config.id,
|
|
8
|
+
_config: config,
|
|
9
|
+
async trigger(payload, options = {}) {
|
|
10
|
+
const validated = config.schema ? config.schema.parse(payload) : payload;
|
|
11
|
+
let run;
|
|
12
|
+
try {
|
|
13
|
+
run = await client.runs.create({
|
|
14
|
+
triggerId: config.id,
|
|
15
|
+
payload: validated,
|
|
16
|
+
idempotencyKey: options.idempotencyKey,
|
|
17
|
+
webhook: options.webhook,
|
|
18
|
+
tags: options.tags,
|
|
19
|
+
priority: options.priority,
|
|
20
|
+
scheduledAt: options.scheduledAt ? new Date(options.scheduledAt) : undefined,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
+
throw new StepplerError(`Failed to trigger task "${config.id}": ${msg}. Check your STEPPLER_API_KEY and network connectivity.`);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
id: run.id,
|
|
29
|
+
status: run.status,
|
|
30
|
+
triggerId: run.triggerId,
|
|
31
|
+
createdAt: run.createdAt,
|
|
32
|
+
cancel: async () => {
|
|
33
|
+
try {
|
|
34
|
+
await client.runs.cancel({ id: run.id });
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38
|
+
throw new StepplerError(`Failed to cancel run "${run.id}": ${msg}`);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
async triggerAndWait(payload, options = {}) {
|
|
44
|
+
const run = await this.trigger(payload, options);
|
|
45
|
+
const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : null;
|
|
46
|
+
while (true) {
|
|
47
|
+
if (deadline && Date.now() > deadline) {
|
|
48
|
+
throw new StepplerError(`triggerAndWait timed out after ${options.timeoutMs}ms (run ${run.id} still running)`);
|
|
49
|
+
}
|
|
50
|
+
let status;
|
|
51
|
+
try {
|
|
52
|
+
status = await client.runs.status({ id: run.id });
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
await sleep(1000 + Math.random() * 500);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (status.status === "success" || status.status === "failed") {
|
|
59
|
+
return {
|
|
60
|
+
id: status.id,
|
|
61
|
+
status: status.status,
|
|
62
|
+
result: status.result,
|
|
63
|
+
error: status.error,
|
|
64
|
+
finishedAt: status.finishedAt ?? null,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
await sleep(800 + Math.random() * 500);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ZodSchema } from "zod";
|
|
2
|
+
export type RunStatus = "pending" | "running" | "success" | "failed";
|
|
3
|
+
export type Run = {
|
|
4
|
+
id: string;
|
|
5
|
+
status: RunStatus;
|
|
6
|
+
triggerId: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
cancel: () => Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
export type RunResult = {
|
|
11
|
+
id: string;
|
|
12
|
+
status: RunStatus;
|
|
13
|
+
result: unknown;
|
|
14
|
+
error: unknown;
|
|
15
|
+
finishedAt: string | null;
|
|
16
|
+
};
|
|
17
|
+
export type TriggerOptions = {
|
|
18
|
+
idempotencyKey?: string;
|
|
19
|
+
webhook?: string;
|
|
20
|
+
tags?: string[];
|
|
21
|
+
priority?: number;
|
|
22
|
+
scheduledAt?: string;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
};
|
|
25
|
+
export type StepHelpers = {
|
|
26
|
+
run: <T>(id: string, fn: () => Promise<T>) => Promise<T>;
|
|
27
|
+
sleep: (id: string, duration: string) => Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
export type RunContext<TPayload = unknown> = {
|
|
30
|
+
payload: TPayload;
|
|
31
|
+
runId: string;
|
|
32
|
+
attempt: number;
|
|
33
|
+
step: StepHelpers;
|
|
34
|
+
};
|
|
35
|
+
export type TaskConfig<TPayload = unknown, TResult = unknown> = {
|
|
36
|
+
id: string;
|
|
37
|
+
name?: string;
|
|
38
|
+
type?: "manual" | "cron" | "event";
|
|
39
|
+
schema?: ZodSchema<TPayload>;
|
|
40
|
+
cron?: string | {
|
|
41
|
+
pattern: string;
|
|
42
|
+
timezone?: string;
|
|
43
|
+
};
|
|
44
|
+
queue?: {
|
|
45
|
+
name: string;
|
|
46
|
+
concurrencyLimit?: number;
|
|
47
|
+
rateLimit?: {
|
|
48
|
+
max: number;
|
|
49
|
+
duration: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
retry?: {
|
|
53
|
+
maxAttempts: number;
|
|
54
|
+
backoff?: "exponential" | "linear" | "fixed";
|
|
55
|
+
minTimeoutInMs?: number;
|
|
56
|
+
maxTimeoutInMs?: number;
|
|
57
|
+
};
|
|
58
|
+
timeout?: number;
|
|
59
|
+
run: (context: RunContext<TPayload>) => Promise<TResult>;
|
|
60
|
+
};
|
|
61
|
+
export type Task<TPayload = unknown, TResult = unknown> = {
|
|
62
|
+
id: string;
|
|
63
|
+
_config: TaskConfig<TPayload, TResult>;
|
|
64
|
+
trigger: (payload: TPayload, options?: TriggerOptions) => Promise<Run>;
|
|
65
|
+
triggerAndWait: (payload: TPayload, options?: TriggerOptions) => Promise<RunResult>;
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@steadlake/run",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist"
|
|
6
|
+
],
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./next": {
|
|
16
|
+
"import": "./dist/adapters/next.js",
|
|
17
|
+
"types": "./dist/adapters/next.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./handler": {
|
|
20
|
+
"import": "./dist/adapters/generic.js",
|
|
21
|
+
"types": "./dist/adapters/generic.d.ts"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"dev": "tsc --watch",
|
|
30
|
+
"format": "oxfmt .",
|
|
31
|
+
"format:check": "oxfmt --check ."
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@steppler/api": "^0.2.5",
|
|
35
|
+
"oxfmt": "^0.44.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^25.5.2",
|
|
39
|
+
"typescript": "^6.0.2"
|
|
40
|
+
}
|
|
41
|
+
}
|