sst 4.6.10 → 4.7.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/dist/aws/bus.d.ts +2 -2
- package/dist/aws/bus.js +3 -12
- package/dist/aws/client.d.ts +10 -1
- package/dist/aws/client.js +33 -17
- package/dist/aws/task.d.ts +2 -2
- package/dist/aws/task.js +7 -24
- package/dist/aws/workflow.d.ts +324 -0
- package/dist/aws/workflow.js +504 -0
- package/dist/vector/index.js +2 -2
- package/package.json +11 -9
package/dist/aws/bus.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aws } from "./client.js";
|
|
2
2
|
import { Resource } from "../resource.js";
|
|
3
3
|
import { event } from "../event/index.js";
|
|
4
4
|
import { EventBridgeEvent, EventBridgeHandler, Context } from "aws-lambda";
|
|
@@ -19,7 +19,7 @@ export declare namespace bus {
|
|
|
19
19
|
name: string;
|
|
20
20
|
}, def: Definition | string, properties: Definition["$input"], options?: {
|
|
21
21
|
metadata?: Definition["$metadata"];
|
|
22
|
-
aws?:
|
|
22
|
+
aws?: aws.Options;
|
|
23
23
|
}): Promise<any>;
|
|
24
24
|
class PublishError extends Error {
|
|
25
25
|
readonly response: Response;
|
package/dist/aws/bus.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aws } from "./client.js";
|
|
2
2
|
import { Resource } from "../resource.js";
|
|
3
3
|
export var bus;
|
|
4
4
|
(function (bus) {
|
|
5
|
-
function url(region, options) {
|
|
6
|
-
if (options?.region)
|
|
7
|
-
region = options.region;
|
|
8
|
-
return `https://events.${region}.amazonaws.com/`;
|
|
9
|
-
}
|
|
10
5
|
function subscriber(_events, cb) {
|
|
11
6
|
return async function (event, context) {
|
|
12
7
|
const payload = {
|
|
@@ -23,8 +18,6 @@ export var bus;
|
|
|
23
18
|
* */
|
|
24
19
|
bus.handler = subscriber;
|
|
25
20
|
async function publish(name, def, properties, options) {
|
|
26
|
-
const c = await client();
|
|
27
|
-
const u = url(c.region, options?.aws);
|
|
28
21
|
const evt = typeof def === "string"
|
|
29
22
|
? {
|
|
30
23
|
type: def,
|
|
@@ -32,10 +25,8 @@ export var bus;
|
|
|
32
25
|
metadata: options?.metadata || {},
|
|
33
26
|
}
|
|
34
27
|
: await def.create(properties, options?.metadata);
|
|
35
|
-
const res = await
|
|
36
|
-
.fetch(u, {
|
|
28
|
+
const res = await aws.fetch("events", "/", {
|
|
37
29
|
method: "POST",
|
|
38
|
-
aws: options?.aws,
|
|
39
30
|
headers: {
|
|
40
31
|
"X-Amz-Target": "AWSEvents.PutEvents",
|
|
41
32
|
"Content-Type": "application/x-amz-json-1.1",
|
|
@@ -53,7 +44,7 @@ export var bus;
|
|
|
53
44
|
},
|
|
54
45
|
],
|
|
55
46
|
}),
|
|
56
|
-
})
|
|
47
|
+
}, options)
|
|
57
48
|
.catch((e) => {
|
|
58
49
|
if (e instanceof Error)
|
|
59
50
|
console.log("cause", e.cause);
|
package/dist/aws/client.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
import { AwsClient } from "aws4fetch";
|
|
2
|
+
type AwsFetchOptions = Exclude<Parameters<AwsClient["fetch"]>[1], null | undefined>;
|
|
3
|
+
export declare namespace aws {
|
|
4
|
+
type Options = Exclude<Parameters<AwsClient["fetch"]>[1], null | undefined>["aws"];
|
|
5
|
+
function client(): Promise<AwsClient>;
|
|
6
|
+
function fetch(service: string, path: string, init: Omit<AwsFetchOptions, "aws">, options?: {
|
|
7
|
+
aws?: Options;
|
|
8
|
+
}): Promise<Response>;
|
|
9
|
+
}
|
|
2
10
|
export declare function client(): Promise<AwsClient>;
|
|
3
|
-
export type AwsOptions =
|
|
11
|
+
export type AwsOptions = aws.Options;
|
|
12
|
+
export {};
|
package/dist/aws/client.js
CHANGED
|
@@ -13,24 +13,40 @@ async function getCredentials(url) {
|
|
|
13
13
|
cachedCredentials = credentials;
|
|
14
14
|
return credentials;
|
|
15
15
|
}
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
export var aws;
|
|
17
|
+
(function (aws) {
|
|
18
|
+
async function client() {
|
|
19
|
+
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
|
|
20
|
+
return new AwsClient({
|
|
21
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
22
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
23
|
+
sessionToken: process.env.AWS_SESSION_TOKEN,
|
|
24
|
+
region: process.env.AWS_REGION,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
|
|
28
|
+
const credentials = await getCredentials("http://169.254.170.2" +
|
|
29
|
+
process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
|
|
30
|
+
return new AwsClient({
|
|
31
|
+
accessKeyId: credentials.AccessKeyId,
|
|
32
|
+
secretAccessKey: credentials.SecretAccessKey,
|
|
33
|
+
sessionToken: credentials.Token,
|
|
34
|
+
region: process.env.AWS_REGION,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
throw new Error("No AWS credentials found");
|
|
24
38
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
region: process.env.AWS_REGION,
|
|
39
|
+
aws.client = client;
|
|
40
|
+
async function fetch(service, path, init, options) {
|
|
41
|
+
const c = await client();
|
|
42
|
+
const region = options?.aws?.region ?? c.region;
|
|
43
|
+
return c.fetch(`https://${service}.${region}.amazonaws.com${path}`, {
|
|
44
|
+
...init,
|
|
45
|
+
aws: options?.aws,
|
|
33
46
|
});
|
|
34
47
|
}
|
|
35
|
-
|
|
48
|
+
aws.fetch = fetch;
|
|
49
|
+
})(aws || (aws = {}));
|
|
50
|
+
export async function client() {
|
|
51
|
+
return aws.client();
|
|
36
52
|
}
|
package/dist/aws/task.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aws } from "./client.js";
|
|
2
2
|
/**
|
|
3
3
|
* The `task` client SDK is available through the following.
|
|
4
4
|
*
|
|
@@ -61,7 +61,7 @@ export declare namespace task {
|
|
|
61
61
|
* Configure the options for the [aws4fetch](https://github.com/mhart/aws4fetch)
|
|
62
62
|
* [`AWSClient`](https://github.com/mhart/aws4fetch?tab=readme-ov-file#new-awsclientoptions) used internally by the SDK.
|
|
63
63
|
*/
|
|
64
|
-
aws?:
|
|
64
|
+
aws?: aws.Options;
|
|
65
65
|
}
|
|
66
66
|
export interface RunOptions extends Options {
|
|
67
67
|
/**
|
package/dist/aws/task.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aws } from "./client.js";
|
|
2
2
|
/**
|
|
3
3
|
* The `task` client SDK is available through the following.
|
|
4
4
|
*
|
|
@@ -13,11 +13,6 @@ import { client } from "./client.js";
|
|
|
13
13
|
*/
|
|
14
14
|
export var task;
|
|
15
15
|
(function (task_1) {
|
|
16
|
-
function url(region, options) {
|
|
17
|
-
if (options?.region)
|
|
18
|
-
region = options.region;
|
|
19
|
-
return `https://ecs.${region}.amazonaws.com/`;
|
|
20
|
-
}
|
|
21
16
|
/**
|
|
22
17
|
* Get the details of a given task.
|
|
23
18
|
*
|
|
@@ -45,11 +40,8 @@ export var task;
|
|
|
45
40
|
* [`DescribeTasks`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_DescribeTasks.html).
|
|
46
41
|
*/
|
|
47
42
|
async function describe(resource, task, options) {
|
|
48
|
-
const
|
|
49
|
-
const u = url(c.region, options?.aws);
|
|
50
|
-
const res = await c.fetch(u, {
|
|
43
|
+
const res = await aws.fetch("ecs", "/", {
|
|
51
44
|
method: "POST",
|
|
52
|
-
aws: options?.aws,
|
|
53
45
|
headers: {
|
|
54
46
|
"X-Amz-Target": "AmazonEC2ContainerServiceV20141113.DescribeTasks",
|
|
55
47
|
"Content-Type": "application/x-amz-json-1.1",
|
|
@@ -58,7 +50,7 @@ export var task;
|
|
|
58
50
|
cluster: resource.cluster,
|
|
59
51
|
tasks: [task],
|
|
60
52
|
}),
|
|
61
|
-
});
|
|
53
|
+
}, options);
|
|
62
54
|
if (!res.ok)
|
|
63
55
|
throw new DescribeError(res);
|
|
64
56
|
const data = (await res.json());
|
|
@@ -110,11 +102,8 @@ export var task;
|
|
|
110
102
|
* [`RunTask`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html).
|
|
111
103
|
*/
|
|
112
104
|
async function run(resource, environment, options) {
|
|
113
|
-
const
|
|
114
|
-
const u = url(c.region, options?.aws);
|
|
115
|
-
const res = await c.fetch(u, {
|
|
105
|
+
const res = await aws.fetch("ecs", "/", {
|
|
116
106
|
method: "POST",
|
|
117
|
-
aws: options?.aws,
|
|
118
107
|
headers: {
|
|
119
108
|
"X-Amz-Target": "AmazonEC2ContainerServiceV20141113.RunTask",
|
|
120
109
|
"Content-Type": "application/x-amz-json-1.1",
|
|
@@ -148,7 +137,7 @@ export var task;
|
|
|
148
137
|
})),
|
|
149
138
|
},
|
|
150
139
|
}),
|
|
151
|
-
});
|
|
140
|
+
}, options);
|
|
152
141
|
if (!res.ok)
|
|
153
142
|
throw new RunError(res);
|
|
154
143
|
const data = (await res.json());
|
|
@@ -193,11 +182,8 @@ export var task;
|
|
|
193
182
|
* [`StopTask`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_StopTask.html).
|
|
194
183
|
*/
|
|
195
184
|
async function stop(resource, task, options) {
|
|
196
|
-
const
|
|
197
|
-
const u = url(c.region, options?.aws);
|
|
198
|
-
const res = await c.fetch(u, {
|
|
185
|
+
const res = await aws.fetch("ecs", "/", {
|
|
199
186
|
method: "POST",
|
|
200
|
-
aws: options?.aws,
|
|
201
187
|
headers: {
|
|
202
188
|
"X-Amz-Target": "AmazonEC2ContainerServiceV20141113.StopTask",
|
|
203
189
|
"Content-Type": "application/x-amz-json-1.1",
|
|
@@ -206,7 +192,7 @@ export var task;
|
|
|
206
192
|
cluster: resource.cluster,
|
|
207
193
|
task,
|
|
208
194
|
}),
|
|
209
|
-
});
|
|
195
|
+
}, options);
|
|
210
196
|
if (!res.ok)
|
|
211
197
|
throw new StopError(res);
|
|
212
198
|
const data = (await res.json());
|
|
@@ -224,7 +210,6 @@ export var task;
|
|
|
224
210
|
constructor(response) {
|
|
225
211
|
super("Failed to describe task");
|
|
226
212
|
this.response = response;
|
|
227
|
-
console.log(response);
|
|
228
213
|
}
|
|
229
214
|
}
|
|
230
215
|
task_1.DescribeError = DescribeError;
|
|
@@ -233,7 +218,6 @@ export var task;
|
|
|
233
218
|
constructor(response) {
|
|
234
219
|
super("Failed to run task");
|
|
235
220
|
this.response = response;
|
|
236
|
-
console.log(response);
|
|
237
221
|
}
|
|
238
222
|
}
|
|
239
223
|
task_1.RunError = RunError;
|
|
@@ -242,7 +226,6 @@ export var task;
|
|
|
242
226
|
constructor(response) {
|
|
243
227
|
super("Failed to stop task");
|
|
244
228
|
this.response = response;
|
|
245
|
-
console.log(response);
|
|
246
229
|
}
|
|
247
230
|
}
|
|
248
231
|
task_1.StopError = StopError;
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import * as durable from "@aws/durable-execution-sdk-js";
|
|
2
|
+
import { aws } from "./client.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `workflow` SDK is a thin wrapper around the
|
|
5
|
+
* [`@aws/durable-execution-sdk-js`](https://www.npmjs.com/package/@aws/durable-execution-sdk-js)
|
|
6
|
+
* package and the AWS Lambda durable execution APIs.
|
|
7
|
+
*
|
|
8
|
+
* SST also adds a few helpers on top, including `ctx.stepWithRollback()`,
|
|
9
|
+
* `ctx.rollbackAll()`, and `ctx.waitUntil()`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts title="src/workflow.ts"
|
|
13
|
+
* import { workflow } from "sst/aws/workflow";
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* Use `stepWithRollback()` and `rollbackAll()` to register compensating actions.
|
|
18
|
+
*
|
|
19
|
+
* ```ts title="src/workflow.ts"
|
|
20
|
+
* import { workflow } from "sst/aws/workflow";
|
|
21
|
+
*
|
|
22
|
+
* export const handler = workflow.handler(async (_event, ctx) => {
|
|
23
|
+
* try {
|
|
24
|
+
* const order = await ctx.stepWithRollback("create-order", {
|
|
25
|
+
* run: async () => ({ orderId: "order_123" }),
|
|
26
|
+
* undo: async (error, result) => {
|
|
27
|
+
* await fetch(`https://example.com/orders/${result.orderId}`, {
|
|
28
|
+
* method: "DELETE",
|
|
29
|
+
* });
|
|
30
|
+
* },
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* await ctx.step("charge-card", async () => {
|
|
34
|
+
* throw new Error("Card declined");
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* return order;
|
|
38
|
+
* } catch (error) {
|
|
39
|
+
* await ctx.rollbackAll(error);
|
|
40
|
+
* throw error;
|
|
41
|
+
* }
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* Use `waitUntil()` when you already know the exact time the workflow should resume.
|
|
47
|
+
*
|
|
48
|
+
* ```ts title="src/workflow.ts"
|
|
49
|
+
* import { workflow } from "sst/aws/workflow";
|
|
50
|
+
*
|
|
51
|
+
* export const handler = workflow.handler(
|
|
52
|
+
* async (_event, ctx) => {
|
|
53
|
+
* const resumeAt = new Date();
|
|
54
|
+
* resumeAt.setMinutes(resumeAt.getMinutes() + 10);
|
|
55
|
+
*
|
|
56
|
+
* await ctx.waitUntil("wait-for-follow-up", resumeAt);
|
|
57
|
+
*
|
|
58
|
+
* return ctx.step("send-follow-up", async () => {
|
|
59
|
+
* return { delivered: true };
|
|
60
|
+
* });
|
|
61
|
+
* },
|
|
62
|
+
* );
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare namespace workflow {
|
|
66
|
+
interface Context<TLogger extends durable.DurableLogger = durable.DurableLogger> extends durable.DurableContext<TLogger> {
|
|
67
|
+
/**
|
|
68
|
+
* Execute a durable step and register a compensating rollback step if it succeeds.
|
|
69
|
+
* If `run` throws, nothing is added to the rollback stack for that step.
|
|
70
|
+
*/
|
|
71
|
+
stepWithRollback<TOutput>(name: string, handler: StepWithRollbackHandler<TOutput, TLogger>, config?: StepConfig<TOutput>): durable.DurablePromise<TOutput>;
|
|
72
|
+
/**
|
|
73
|
+
* Wait until the provided time. Delays are rounded up to the nearest second.
|
|
74
|
+
*/
|
|
75
|
+
waitUntil(name: string, until: Date): durable.DurablePromise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Execute all registered rollback steps in reverse order.
|
|
78
|
+
*/
|
|
79
|
+
rollbackAll(error: unknown): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
type Handler<TEvent = any, TResult = any, TLogger extends durable.DurableLogger = durable.DurableLogger> = (event: TEvent, context: Context<TLogger>) => Promise<TResult>;
|
|
82
|
+
type Config = durable.DurableExecutionConfig;
|
|
83
|
+
type Duration = durable.Duration;
|
|
84
|
+
type StepConfig<TOutput = any> = durable.StepConfig<TOutput>;
|
|
85
|
+
type ExecutionStatus = "RUNNING" | "SUCCEEDED" | "FAILED" | "TIMED_OUT" | "STOPPED";
|
|
86
|
+
interface Resource {
|
|
87
|
+
/**
|
|
88
|
+
* The name of the workflow function.
|
|
89
|
+
*/
|
|
90
|
+
name: string;
|
|
91
|
+
/**
|
|
92
|
+
* The version or alias qualifier to invoke.
|
|
93
|
+
*
|
|
94
|
+
* Linked `sst.aws.Workflow` resources include this automatically.
|
|
95
|
+
*/
|
|
96
|
+
qualifier: string;
|
|
97
|
+
}
|
|
98
|
+
interface Options {
|
|
99
|
+
/**
|
|
100
|
+
* Configure the options for the [aws4fetch](https://github.com/mhart/aws4fetch)
|
|
101
|
+
* [`AWSClient`](https://github.com/mhart/aws4fetch?tab=readme-ov-file#new-awsclientoptions) used internally by the SDK.
|
|
102
|
+
*/
|
|
103
|
+
aws?: aws.Options;
|
|
104
|
+
}
|
|
105
|
+
interface StartResponse {
|
|
106
|
+
/**
|
|
107
|
+
* The ARN of the durable execution.
|
|
108
|
+
*/
|
|
109
|
+
arn?: string;
|
|
110
|
+
/**
|
|
111
|
+
* The HTTP status code from Lambda.
|
|
112
|
+
*/
|
|
113
|
+
statusCode: number;
|
|
114
|
+
/**
|
|
115
|
+
* The function version that was executed.
|
|
116
|
+
*/
|
|
117
|
+
version?: string;
|
|
118
|
+
}
|
|
119
|
+
interface Execution {
|
|
120
|
+
/**
|
|
121
|
+
* The ARN of the durable execution.
|
|
122
|
+
*/
|
|
123
|
+
arn: string;
|
|
124
|
+
/**
|
|
125
|
+
* The durable execution name.
|
|
126
|
+
*/
|
|
127
|
+
name: string;
|
|
128
|
+
/**
|
|
129
|
+
* The ARN of the workflow function.
|
|
130
|
+
*/
|
|
131
|
+
functionArn: string;
|
|
132
|
+
/**
|
|
133
|
+
* The current execution status.
|
|
134
|
+
*/
|
|
135
|
+
status: ExecutionStatus;
|
|
136
|
+
/**
|
|
137
|
+
* When the execution started.
|
|
138
|
+
*/
|
|
139
|
+
createdAt: Date;
|
|
140
|
+
/**
|
|
141
|
+
* When the execution ended, if it has finished.
|
|
142
|
+
*/
|
|
143
|
+
endedAt?: Date;
|
|
144
|
+
}
|
|
145
|
+
interface ListResponse {
|
|
146
|
+
/**
|
|
147
|
+
* The matching executions.
|
|
148
|
+
*/
|
|
149
|
+
executions: Execution[];
|
|
150
|
+
}
|
|
151
|
+
interface DescribeResponse extends Execution {
|
|
152
|
+
/**
|
|
153
|
+
* The version that started the execution.
|
|
154
|
+
*/
|
|
155
|
+
version?: string;
|
|
156
|
+
}
|
|
157
|
+
interface StopResponse {
|
|
158
|
+
/**
|
|
159
|
+
* The ARN of the durable execution.
|
|
160
|
+
*/
|
|
161
|
+
arn: string;
|
|
162
|
+
/**
|
|
163
|
+
* The execution status after the stop call.
|
|
164
|
+
*/
|
|
165
|
+
status: "STOPPED";
|
|
166
|
+
/**
|
|
167
|
+
* When the execution was stopped.
|
|
168
|
+
*/
|
|
169
|
+
stoppedAt?: Date;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Create a durable workflow handler.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts title="src/workflow.ts"
|
|
176
|
+
* import { workflow } from "sst/aws/workflow";
|
|
177
|
+
*
|
|
178
|
+
* export const handler = workflow.handler(
|
|
179
|
+
* async (_event, ctx) => {
|
|
180
|
+
* const user = await ctx.step("load-user", async () => {
|
|
181
|
+
* return { id: "user_123", email: "alice@example.com" };
|
|
182
|
+
* });
|
|
183
|
+
*
|
|
184
|
+
* await ctx.wait("pause-before-email", "1 minute");
|
|
185
|
+
*
|
|
186
|
+
* return ctx.step("send-email", async () => {
|
|
187
|
+
* return { sent: true, userId: user.id };
|
|
188
|
+
* });
|
|
189
|
+
* },
|
|
190
|
+
* );
|
|
191
|
+
* ```
|
|
192
|
+
*/
|
|
193
|
+
function handler<TEvent = any, TResult = any, TLogger extends durable.DurableLogger = durable.DurableLogger>(input: Handler<TEvent, TResult, TLogger>, config?: Config): durable.DurableLambdaHandler;
|
|
194
|
+
/**
|
|
195
|
+
* Start a new workflow execution.
|
|
196
|
+
*
|
|
197
|
+
* This is the equivalent to calling
|
|
198
|
+
* [`Invoke`](https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html)
|
|
199
|
+
* for a durable Lambda function, using the durable invocation flow described in
|
|
200
|
+
* [Invoking durable Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/durable-invoking.html).
|
|
201
|
+
*/
|
|
202
|
+
function start<TPayload = unknown>(resource: Resource, input: StartInput<TPayload>, options?: Options): Promise<StartResponse>;
|
|
203
|
+
/**
|
|
204
|
+
* List workflow executions.
|
|
205
|
+
*
|
|
206
|
+
* The SDK returns only the first page of results.
|
|
207
|
+
*/
|
|
208
|
+
function list(resource: Resource, query: ListQuery, options?: Options): Promise<ListResponse>;
|
|
209
|
+
/**
|
|
210
|
+
* Get the details for a single workflow execution.
|
|
211
|
+
*/
|
|
212
|
+
function describe(arn: string, options?: Options): Promise<DescribeResponse>;
|
|
213
|
+
/**
|
|
214
|
+
* Stop a running workflow execution.
|
|
215
|
+
*/
|
|
216
|
+
function stop(arn: string, input?: StopInput, options?: Options): Promise<StopResponse>;
|
|
217
|
+
/**
|
|
218
|
+
* Send a successful result for a pending workflow callback.
|
|
219
|
+
*
|
|
220
|
+
* This is the equivalent to calling
|
|
221
|
+
* [`SendDurableExecutionCallbackSuccess`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackSuccess.html).
|
|
222
|
+
*/
|
|
223
|
+
function succeed<TPayload = unknown>(token: string, input?: SucceedInput<TPayload>, options?: Options): Promise<void>;
|
|
224
|
+
/**
|
|
225
|
+
* Send a failure result for a pending workflow callback.
|
|
226
|
+
*
|
|
227
|
+
* This is the equivalent to calling
|
|
228
|
+
* [`SendDurableExecutionCallbackFailure`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackFailure.html).
|
|
229
|
+
*/
|
|
230
|
+
function fail(token: string, input: FailInput, options?: Options): Promise<void>;
|
|
231
|
+
/**
|
|
232
|
+
* Send a heartbeat for a pending workflow callback.
|
|
233
|
+
*
|
|
234
|
+
* This is useful when the external system handling the callback is still doing
|
|
235
|
+
* work and needs to prevent the callback from timing out.
|
|
236
|
+
*
|
|
237
|
+
* This is the equivalent to calling
|
|
238
|
+
* [`SendDurableExecutionCallbackHeartbeat`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackHeartbeat.html).
|
|
239
|
+
*/
|
|
240
|
+
function heartbeat(token: string, options?: Options): Promise<void>;
|
|
241
|
+
class StartError extends Error {
|
|
242
|
+
readonly response: Response;
|
|
243
|
+
constructor(response: Response);
|
|
244
|
+
}
|
|
245
|
+
class ListError extends Error {
|
|
246
|
+
readonly response: Response;
|
|
247
|
+
constructor(response: Response);
|
|
248
|
+
}
|
|
249
|
+
class DescribeError extends Error {
|
|
250
|
+
readonly response: Response;
|
|
251
|
+
constructor(response: Response);
|
|
252
|
+
}
|
|
253
|
+
class StopError extends Error {
|
|
254
|
+
readonly response: Response;
|
|
255
|
+
constructor(response: Response);
|
|
256
|
+
}
|
|
257
|
+
class SucceedError extends Error {
|
|
258
|
+
readonly response: Response;
|
|
259
|
+
constructor(response: Response);
|
|
260
|
+
}
|
|
261
|
+
class FailError extends Error {
|
|
262
|
+
readonly response: Response;
|
|
263
|
+
constructor(response: Response);
|
|
264
|
+
}
|
|
265
|
+
class HeartbeatError extends Error {
|
|
266
|
+
readonly response: Response;
|
|
267
|
+
constructor(response: Response);
|
|
268
|
+
}
|
|
269
|
+
class RollbackError extends Error {
|
|
270
|
+
readonly stepName: string;
|
|
271
|
+
readonly originalError: unknown;
|
|
272
|
+
readonly undoError: unknown;
|
|
273
|
+
constructor(stepName: string, originalError: unknown, undoError: unknown);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
interface StepWithRollbackHandler<TOutput = any, TLogger extends durable.DurableLogger = durable.DurableLogger> {
|
|
277
|
+
/**
|
|
278
|
+
* The durable step to execute.
|
|
279
|
+
*/
|
|
280
|
+
run: durable.StepFunc<TOutput, TLogger>;
|
|
281
|
+
/**
|
|
282
|
+
* Called during rollback with the original error, the step result, and step context.
|
|
283
|
+
*/
|
|
284
|
+
undo: (error: unknown, value: TOutput, context: Parameters<durable.StepFunc<void, TLogger>>[0]) => Promise<void>;
|
|
285
|
+
}
|
|
286
|
+
interface StartInput<TPayload = unknown> {
|
|
287
|
+
/**
|
|
288
|
+
* The unique name for this workflow execution.
|
|
289
|
+
*/
|
|
290
|
+
name: string;
|
|
291
|
+
/**
|
|
292
|
+
* The event payload passed to the workflow handler.
|
|
293
|
+
*/
|
|
294
|
+
payload?: TPayload;
|
|
295
|
+
}
|
|
296
|
+
interface SucceedInput<TPayload = unknown> {
|
|
297
|
+
/**
|
|
298
|
+
* The payload to resolve the callback with.
|
|
299
|
+
*/
|
|
300
|
+
payload?: TPayload;
|
|
301
|
+
}
|
|
302
|
+
interface FailInput {
|
|
303
|
+
/**
|
|
304
|
+
* The error to reject the callback with. Supports an `Error`, a string,
|
|
305
|
+
* or an object with camelCase fields like `message`, `type`, `data`, and `stack`.
|
|
306
|
+
*/
|
|
307
|
+
error: unknown;
|
|
308
|
+
}
|
|
309
|
+
interface StopInput {
|
|
310
|
+
/**
|
|
311
|
+
* The error to reject the callback with. Supports an `Error`, a string,
|
|
312
|
+
* or an object with camelCase fields like `message`, `type`, `data`, and `stack`.
|
|
313
|
+
*/
|
|
314
|
+
error?: unknown;
|
|
315
|
+
}
|
|
316
|
+
interface ListQuery {
|
|
317
|
+
status?: workflow.ExecutionStatus;
|
|
318
|
+
createdAt?: {
|
|
319
|
+
from?: Date;
|
|
320
|
+
to?: Date;
|
|
321
|
+
order?: "asc" | "desc";
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
export {};
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import * as durable from "@aws/durable-execution-sdk-js";
|
|
2
|
+
import { aws } from "./client.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `workflow` SDK is a thin wrapper around the
|
|
5
|
+
* [`@aws/durable-execution-sdk-js`](https://www.npmjs.com/package/@aws/durable-execution-sdk-js)
|
|
6
|
+
* package and the AWS Lambda durable execution APIs.
|
|
7
|
+
*
|
|
8
|
+
* SST also adds a few helpers on top, including `ctx.stepWithRollback()`,
|
|
9
|
+
* `ctx.rollbackAll()`, and `ctx.waitUntil()`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts title="src/workflow.ts"
|
|
13
|
+
* import { workflow } from "sst/aws/workflow";
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* Use `stepWithRollback()` and `rollbackAll()` to register compensating actions.
|
|
18
|
+
*
|
|
19
|
+
* ```ts title="src/workflow.ts"
|
|
20
|
+
* import { workflow } from "sst/aws/workflow";
|
|
21
|
+
*
|
|
22
|
+
* export const handler = workflow.handler(async (_event, ctx) => {
|
|
23
|
+
* try {
|
|
24
|
+
* const order = await ctx.stepWithRollback("create-order", {
|
|
25
|
+
* run: async () => ({ orderId: "order_123" }),
|
|
26
|
+
* undo: async (error, result) => {
|
|
27
|
+
* await fetch(`https://example.com/orders/${result.orderId}`, {
|
|
28
|
+
* method: "DELETE",
|
|
29
|
+
* });
|
|
30
|
+
* },
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* await ctx.step("charge-card", async () => {
|
|
34
|
+
* throw new Error("Card declined");
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* return order;
|
|
38
|
+
* } catch (error) {
|
|
39
|
+
* await ctx.rollbackAll(error);
|
|
40
|
+
* throw error;
|
|
41
|
+
* }
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* Use `waitUntil()` when you already know the exact time the workflow should resume.
|
|
47
|
+
*
|
|
48
|
+
* ```ts title="src/workflow.ts"
|
|
49
|
+
* import { workflow } from "sst/aws/workflow";
|
|
50
|
+
*
|
|
51
|
+
* export const handler = workflow.handler(
|
|
52
|
+
* async (_event, ctx) => {
|
|
53
|
+
* const resumeAt = new Date();
|
|
54
|
+
* resumeAt.setMinutes(resumeAt.getMinutes() + 10);
|
|
55
|
+
*
|
|
56
|
+
* await ctx.waitUntil("wait-for-follow-up", resumeAt);
|
|
57
|
+
*
|
|
58
|
+
* return ctx.step("send-follow-up", async () => {
|
|
59
|
+
* return { delivered: true };
|
|
60
|
+
* });
|
|
61
|
+
* },
|
|
62
|
+
* );
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export var workflow;
|
|
66
|
+
(function (workflow) {
|
|
67
|
+
/**
|
|
68
|
+
* Create a durable workflow handler.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts title="src/workflow.ts"
|
|
72
|
+
* import { workflow } from "sst/aws/workflow";
|
|
73
|
+
*
|
|
74
|
+
* export const handler = workflow.handler(
|
|
75
|
+
* async (_event, ctx) => {
|
|
76
|
+
* const user = await ctx.step("load-user", async () => {
|
|
77
|
+
* return { id: "user_123", email: "alice@example.com" };
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* await ctx.wait("pause-before-email", "1 minute");
|
|
81
|
+
*
|
|
82
|
+
* return ctx.step("send-email", async () => {
|
|
83
|
+
* return { sent: true, userId: user.id };
|
|
84
|
+
* });
|
|
85
|
+
* },
|
|
86
|
+
* );
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
function handler(input, config) {
|
|
90
|
+
return durable.withDurableExecution((event, context) => input(event, withRollback(context)), config);
|
|
91
|
+
}
|
|
92
|
+
workflow.handler = handler;
|
|
93
|
+
/**
|
|
94
|
+
* Start a new workflow execution.
|
|
95
|
+
*
|
|
96
|
+
* This is the equivalent to calling
|
|
97
|
+
* [`Invoke`](https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html)
|
|
98
|
+
* for a durable Lambda function, using the durable invocation flow described in
|
|
99
|
+
* [Invoking durable Lambda functions](https://docs.aws.amazon.com/lambda/latest/dg/durable-invoking.html).
|
|
100
|
+
*/
|
|
101
|
+
async function start(resource, input, options) {
|
|
102
|
+
const query = new URLSearchParams({
|
|
103
|
+
Qualifier: resource.qualifier,
|
|
104
|
+
});
|
|
105
|
+
const response = await aws.fetch("lambda", `/2015-03-31/functions/${encodeURIComponent(resource.name)}/invocations?${query.toString()}`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "application/json",
|
|
109
|
+
"X-Amz-Durable-Execution-Name": input.name,
|
|
110
|
+
"X-Amz-Invocation-Type": "Event",
|
|
111
|
+
},
|
|
112
|
+
body: input.payload === undefined
|
|
113
|
+
? undefined
|
|
114
|
+
: JSON.stringify(input.payload),
|
|
115
|
+
}, options);
|
|
116
|
+
if (!response.ok)
|
|
117
|
+
throw new StartError(response);
|
|
118
|
+
return {
|
|
119
|
+
arn: response.headers.get("X-Amz-Durable-Execution-Arn") ?? undefined,
|
|
120
|
+
statusCode: response.status,
|
|
121
|
+
version: response.headers.get("X-Amz-Executed-Version") ?? undefined,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
workflow.start = start;
|
|
125
|
+
/**
|
|
126
|
+
* List workflow executions.
|
|
127
|
+
*
|
|
128
|
+
* The SDK returns only the first page of results.
|
|
129
|
+
*/
|
|
130
|
+
async function list(resource, query, options) {
|
|
131
|
+
const startedAfter = query.createdAt?.from?.toISOString();
|
|
132
|
+
const startedBefore = query.createdAt?.to?.toISOString();
|
|
133
|
+
const direction = query.createdAt?.order ?? "asc";
|
|
134
|
+
const status = query.status;
|
|
135
|
+
if (startedAfter && startedBefore && startedAfter > startedBefore) {
|
|
136
|
+
throw new TypeError("workflow.list createdAt.from must be before createdAt.to");
|
|
137
|
+
}
|
|
138
|
+
if (direction !== "asc" && direction !== "desc") {
|
|
139
|
+
throw new TypeError(`Unsupported workflow order direction '${direction}'`);
|
|
140
|
+
}
|
|
141
|
+
const params = new URLSearchParams({
|
|
142
|
+
MaxItems: String(workflowListPageSize),
|
|
143
|
+
Qualifier: resource.qualifier,
|
|
144
|
+
});
|
|
145
|
+
if (Array.isArray(status)) {
|
|
146
|
+
throw new TypeError("workflow.list status must be a single status");
|
|
147
|
+
}
|
|
148
|
+
if (status)
|
|
149
|
+
params.append("Statuses", status);
|
|
150
|
+
if (startedAfter)
|
|
151
|
+
params.set("StartedAfter", startedAfter);
|
|
152
|
+
if (startedBefore)
|
|
153
|
+
params.set("StartedBefore", startedBefore);
|
|
154
|
+
if (direction === "desc")
|
|
155
|
+
params.set("ReverseOrder", "true");
|
|
156
|
+
const response = await aws.fetch("lambda", `/2025-12-01/functions/${encodeURIComponent(resource.name)}/durable-executions?${params.toString()}`, {
|
|
157
|
+
method: "GET",
|
|
158
|
+
}, options);
|
|
159
|
+
if (!response.ok)
|
|
160
|
+
throw new ListError(response);
|
|
161
|
+
const data = (await response.json());
|
|
162
|
+
const executions = Array.isArray(data.DurableExecutions)
|
|
163
|
+
? data.DurableExecutions
|
|
164
|
+
: [];
|
|
165
|
+
return {
|
|
166
|
+
executions: executions.map(parseExecution),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
workflow.list = list;
|
|
170
|
+
/**
|
|
171
|
+
* Get the details for a single workflow execution.
|
|
172
|
+
*/
|
|
173
|
+
async function describe(arn, options) {
|
|
174
|
+
const response = await aws.fetch("lambda", `/2025-12-01/durable-executions/${encodeURIComponent(arn)}`, {
|
|
175
|
+
method: "GET",
|
|
176
|
+
}, options);
|
|
177
|
+
if (!response.ok)
|
|
178
|
+
throw new DescribeError(response);
|
|
179
|
+
const data = (await response.json());
|
|
180
|
+
if (!data.DurableExecutionArn ||
|
|
181
|
+
!data.DurableExecutionName ||
|
|
182
|
+
!data.FunctionArn ||
|
|
183
|
+
data.StartTimestamp === undefined ||
|
|
184
|
+
data.Status === undefined) {
|
|
185
|
+
throw new DescribeError(response);
|
|
186
|
+
}
|
|
187
|
+
const execution = parseExecution(data);
|
|
188
|
+
return {
|
|
189
|
+
...execution,
|
|
190
|
+
version: data.Version,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
workflow.describe = describe;
|
|
194
|
+
/**
|
|
195
|
+
* Stop a running workflow execution.
|
|
196
|
+
*/
|
|
197
|
+
async function stop(arn, input, options) {
|
|
198
|
+
const response = await aws.fetch("lambda", `/2025-12-01/durable-executions/${encodeURIComponent(arn)}/stop`, {
|
|
199
|
+
method: "POST",
|
|
200
|
+
headers: input?.error
|
|
201
|
+
? {
|
|
202
|
+
"Content-Type": "application/json",
|
|
203
|
+
}
|
|
204
|
+
: undefined,
|
|
205
|
+
body: input?.error === undefined
|
|
206
|
+
? undefined
|
|
207
|
+
: JSON.stringify(normalizeError(input.error)),
|
|
208
|
+
}, options);
|
|
209
|
+
if (!response.ok)
|
|
210
|
+
throw new StopError(response);
|
|
211
|
+
const data = (await response.json());
|
|
212
|
+
return {
|
|
213
|
+
arn,
|
|
214
|
+
status: "STOPPED",
|
|
215
|
+
stoppedAt: data.StopTimestamp === undefined
|
|
216
|
+
? undefined
|
|
217
|
+
: parseTimestamp(data.StopTimestamp),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
workflow.stop = stop;
|
|
221
|
+
/**
|
|
222
|
+
* Send a successful result for a pending workflow callback.
|
|
223
|
+
*
|
|
224
|
+
* This is the equivalent to calling
|
|
225
|
+
* [`SendDurableExecutionCallbackSuccess`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackSuccess.html).
|
|
226
|
+
*/
|
|
227
|
+
async function succeed(token, input = {}, options) {
|
|
228
|
+
const response = await aws.fetch("lambda", `/2025-12-01/durable-execution-callbacks/${encodeURIComponent(token)}/succeed`, {
|
|
229
|
+
method: "POST",
|
|
230
|
+
headers: {
|
|
231
|
+
"Content-Type": "application/json",
|
|
232
|
+
},
|
|
233
|
+
body: input.payload === undefined
|
|
234
|
+
? undefined
|
|
235
|
+
: JSON.stringify(input.payload),
|
|
236
|
+
}, options);
|
|
237
|
+
if (!response.ok)
|
|
238
|
+
throw new SucceedError(response);
|
|
239
|
+
}
|
|
240
|
+
workflow.succeed = succeed;
|
|
241
|
+
/**
|
|
242
|
+
* Send a failure result for a pending workflow callback.
|
|
243
|
+
*
|
|
244
|
+
* This is the equivalent to calling
|
|
245
|
+
* [`SendDurableExecutionCallbackFailure`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackFailure.html).
|
|
246
|
+
*/
|
|
247
|
+
async function fail(token, input, options) {
|
|
248
|
+
const response = await aws.fetch("lambda", `/2025-12-01/durable-execution-callbacks/${encodeURIComponent(token)}/fail`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
headers: {
|
|
251
|
+
"Content-Type": "application/json",
|
|
252
|
+
},
|
|
253
|
+
body: JSON.stringify(normalizeError(input.error)),
|
|
254
|
+
}, options);
|
|
255
|
+
if (!response.ok)
|
|
256
|
+
throw new FailError(response);
|
|
257
|
+
}
|
|
258
|
+
workflow.fail = fail;
|
|
259
|
+
/**
|
|
260
|
+
* Send a heartbeat for a pending workflow callback.
|
|
261
|
+
*
|
|
262
|
+
* This is useful when the external system handling the callback is still doing
|
|
263
|
+
* work and needs to prevent the callback from timing out.
|
|
264
|
+
*
|
|
265
|
+
* This is the equivalent to calling
|
|
266
|
+
* [`SendDurableExecutionCallbackHeartbeat`](https://docs.aws.amazon.com/lambda/latest/api/API_SendDurableExecutionCallbackHeartbeat.html).
|
|
267
|
+
*/
|
|
268
|
+
async function heartbeat(token, options) {
|
|
269
|
+
const response = await aws.fetch("lambda", `/2025-12-01/durable-execution-callbacks/${encodeURIComponent(token)}/heartbeat`, {
|
|
270
|
+
method: "POST",
|
|
271
|
+
}, options);
|
|
272
|
+
if (!response.ok)
|
|
273
|
+
throw new HeartbeatError(response);
|
|
274
|
+
}
|
|
275
|
+
workflow.heartbeat = heartbeat;
|
|
276
|
+
class StartError extends Error {
|
|
277
|
+
response;
|
|
278
|
+
constructor(response) {
|
|
279
|
+
super("Failed to start workflow");
|
|
280
|
+
this.response = response;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
workflow.StartError = StartError;
|
|
284
|
+
class ListError extends Error {
|
|
285
|
+
response;
|
|
286
|
+
constructor(response) {
|
|
287
|
+
super("Failed to list workflows");
|
|
288
|
+
this.response = response;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
workflow.ListError = ListError;
|
|
292
|
+
class DescribeError extends Error {
|
|
293
|
+
response;
|
|
294
|
+
constructor(response) {
|
|
295
|
+
super("Failed to describe workflow");
|
|
296
|
+
this.response = response;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
workflow.DescribeError = DescribeError;
|
|
300
|
+
class StopError extends Error {
|
|
301
|
+
response;
|
|
302
|
+
constructor(response) {
|
|
303
|
+
super("Failed to stop workflow");
|
|
304
|
+
this.response = response;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
workflow.StopError = StopError;
|
|
308
|
+
class SucceedError extends Error {
|
|
309
|
+
response;
|
|
310
|
+
constructor(response) {
|
|
311
|
+
super("Failed to succeed workflow callback");
|
|
312
|
+
this.response = response;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
workflow.SucceedError = SucceedError;
|
|
316
|
+
class FailError extends Error {
|
|
317
|
+
response;
|
|
318
|
+
constructor(response) {
|
|
319
|
+
super("Failed to fail workflow callback");
|
|
320
|
+
this.response = response;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
workflow.FailError = FailError;
|
|
324
|
+
class HeartbeatError extends Error {
|
|
325
|
+
response;
|
|
326
|
+
constructor(response) {
|
|
327
|
+
super("Failed to heartbeat workflow callback");
|
|
328
|
+
this.response = response;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
workflow.HeartbeatError = HeartbeatError;
|
|
332
|
+
class RollbackError extends Error {
|
|
333
|
+
stepName;
|
|
334
|
+
originalError;
|
|
335
|
+
undoError;
|
|
336
|
+
constructor(stepName, originalError, undoError) {
|
|
337
|
+
super(`Failed to rollback workflow step '${stepName}': ${undoError instanceof Error ? undoError.message : String(undoError)}`);
|
|
338
|
+
this.stepName = stepName;
|
|
339
|
+
this.originalError = originalError;
|
|
340
|
+
this.undoError = undoError;
|
|
341
|
+
this.name = "RollbackError";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
workflow.RollbackError = RollbackError;
|
|
345
|
+
})(workflow || (workflow = {}));
|
|
346
|
+
const workflowListPageSize = 1000;
|
|
347
|
+
const rollbackStateSymbol = Symbol("sst.workflow.rollback.state");
|
|
348
|
+
function normalizeError(error) {
|
|
349
|
+
function serializeErrorData(input) {
|
|
350
|
+
if (input === undefined)
|
|
351
|
+
return undefined;
|
|
352
|
+
if (typeof input === "string")
|
|
353
|
+
return input;
|
|
354
|
+
try {
|
|
355
|
+
return JSON.stringify(input);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return String(input);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function normalizeStack(input) {
|
|
362
|
+
if (typeof input === "string") {
|
|
363
|
+
return input.split("\n").map((line) => line.trim());
|
|
364
|
+
}
|
|
365
|
+
if (Array.isArray(input))
|
|
366
|
+
return input.map(String);
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
if (error === undefined) {
|
|
370
|
+
return {
|
|
371
|
+
ErrorMessage: "Callback failed",
|
|
372
|
+
ErrorType: "Error",
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
if (error instanceof Error) {
|
|
376
|
+
const { message, name, stack, ...rest } = error;
|
|
377
|
+
return {
|
|
378
|
+
ErrorMessage: message,
|
|
379
|
+
ErrorType: name,
|
|
380
|
+
ErrorData: Object.keys(rest).length
|
|
381
|
+
? serializeErrorData(rest)
|
|
382
|
+
: undefined,
|
|
383
|
+
StackTrace: normalizeStack(stack),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (typeof error === "string") {
|
|
387
|
+
return {
|
|
388
|
+
ErrorMessage: error,
|
|
389
|
+
ErrorType: "Error",
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (error === null || typeof error !== "object") {
|
|
393
|
+
return {
|
|
394
|
+
ErrorMessage: String(error),
|
|
395
|
+
ErrorType: "Error",
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const value = error;
|
|
399
|
+
const { data, message, name, stack, type, ...rest } = value;
|
|
400
|
+
const hasKnownFields = message !== undefined ||
|
|
401
|
+
name !== undefined ||
|
|
402
|
+
type !== undefined ||
|
|
403
|
+
data !== undefined ||
|
|
404
|
+
stack !== undefined;
|
|
405
|
+
return {
|
|
406
|
+
ErrorMessage: typeof message === "string" ? message : "Callback failed",
|
|
407
|
+
ErrorType: typeof type === "string"
|
|
408
|
+
? type
|
|
409
|
+
: typeof name === "string"
|
|
410
|
+
? name
|
|
411
|
+
: "Error",
|
|
412
|
+
ErrorData: data !== undefined
|
|
413
|
+
? serializeErrorData(data)
|
|
414
|
+
: Object.keys(rest).length
|
|
415
|
+
? serializeErrorData(rest)
|
|
416
|
+
: hasKnownFields
|
|
417
|
+
? undefined
|
|
418
|
+
: serializeErrorData(error),
|
|
419
|
+
StackTrace: normalizeStack(stack),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function parseExecution(execution) {
|
|
423
|
+
return {
|
|
424
|
+
arn: execution.DurableExecutionArn,
|
|
425
|
+
name: execution.DurableExecutionName,
|
|
426
|
+
functionArn: execution.FunctionArn,
|
|
427
|
+
status: execution.Status,
|
|
428
|
+
createdAt: parseTimestamp(execution.StartTimestamp),
|
|
429
|
+
endedAt: execution.EndTimestamp === undefined
|
|
430
|
+
? undefined
|
|
431
|
+
: parseTimestamp(execution.EndTimestamp),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
function parseTimestamp(timestamp) {
|
|
435
|
+
const value = typeof timestamp === "number" ? timestamp : Number(timestamp);
|
|
436
|
+
if (Number.isFinite(value)) {
|
|
437
|
+
return new Date(value < 1_000_000_000_000 ? value * 1000 : value);
|
|
438
|
+
}
|
|
439
|
+
return new Date(timestamp);
|
|
440
|
+
}
|
|
441
|
+
function resolveWaitUntilDuration(until) {
|
|
442
|
+
const timestamp = until.getTime();
|
|
443
|
+
if (!Number.isFinite(timestamp)) {
|
|
444
|
+
throw new TypeError("waitUntil requires a valid Date");
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
seconds: Math.max(0, Math.ceil((timestamp - Date.now()) / 1000)),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function withRollback(context) {
|
|
451
|
+
const wrapped = context;
|
|
452
|
+
if (wrapped[rollbackStateSymbol])
|
|
453
|
+
return wrapped;
|
|
454
|
+
const rollbackState = { undoStack: [] };
|
|
455
|
+
wrapped[rollbackStateSymbol] = rollbackState;
|
|
456
|
+
Object.defineProperty(wrapped, "stepWithRollback", {
|
|
457
|
+
configurable: true,
|
|
458
|
+
enumerable: false,
|
|
459
|
+
writable: true,
|
|
460
|
+
value: function (name, handler, config) {
|
|
461
|
+
const undoConfig = config?.retryStrategy || config?.semantics
|
|
462
|
+
? {
|
|
463
|
+
retryStrategy: config.retryStrategy,
|
|
464
|
+
semantics: config.semantics,
|
|
465
|
+
}
|
|
466
|
+
: undefined;
|
|
467
|
+
return new durable.DurablePromise(async () => {
|
|
468
|
+
const result = await context.step(name, handler.run, config);
|
|
469
|
+
rollbackState.undoStack.push({
|
|
470
|
+
name,
|
|
471
|
+
execute: async (error, rollbackContext) => {
|
|
472
|
+
await rollbackContext.step(`Undo '${name}'`, (stepContext) => handler.undo(error, result, stepContext), undoConfig);
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
return result;
|
|
476
|
+
});
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
Object.defineProperty(wrapped, "waitUntil", {
|
|
480
|
+
configurable: true,
|
|
481
|
+
enumerable: false,
|
|
482
|
+
writable: true,
|
|
483
|
+
value: (name, until) => context.wait(name, resolveWaitUntilDuration(until)),
|
|
484
|
+
});
|
|
485
|
+
Object.defineProperty(wrapped, "rollbackAll", {
|
|
486
|
+
configurable: true,
|
|
487
|
+
enumerable: false,
|
|
488
|
+
writable: true,
|
|
489
|
+
value: async (error) => {
|
|
490
|
+
while (rollbackState.undoStack.length > 0) {
|
|
491
|
+
const rollbackStep = rollbackState.undoStack.pop();
|
|
492
|
+
if (!rollbackStep)
|
|
493
|
+
continue;
|
|
494
|
+
try {
|
|
495
|
+
await rollbackStep.execute(error, context);
|
|
496
|
+
}
|
|
497
|
+
catch (undoError) {
|
|
498
|
+
throw new workflow.RollbackError(rollbackStep.name, error, undoError);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
return wrapped;
|
|
504
|
+
}
|
package/dist/vector/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Resource } from "../resource.js";
|
|
2
|
-
import {
|
|
2
|
+
import { aws } from "../aws/client.js";
|
|
3
3
|
/**
|
|
4
4
|
* Create a client to interact with the Vector database.
|
|
5
5
|
* @example
|
|
@@ -48,7 +48,7 @@ export function VectorClient(name) {
|
|
|
48
48
|
}
|
|
49
49
|
async function invokeFunction(functionName, body, errorMessage, attempts = 0) {
|
|
50
50
|
try {
|
|
51
|
-
const c = await client();
|
|
51
|
+
const c = await aws.client();
|
|
52
52
|
const endpoint = `https://lambda.${process.env.AWS_REGION}.amazonaws.com/2015-03-31`;
|
|
53
53
|
const response = await c.fetch(`${endpoint}/functions/${functionName}/invocations`, {
|
|
54
54
|
method: "POST",
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "sst",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.7.0",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"release": "./scripts/release.ts"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
+
"@aws/durable-execution-sdk-js-testing": "^1.1.1",
|
|
30
31
|
"@tsconfig/node20": "20.1.4",
|
|
31
32
|
"@types/aws-lambda": "^8.10.155",
|
|
32
33
|
"@types/node": "22.10.0",
|
|
@@ -44,16 +45,17 @@
|
|
|
44
45
|
"sst": "./bin/sst.mjs"
|
|
45
46
|
},
|
|
46
47
|
"optionalDependencies": {
|
|
47
|
-
"sst-linux-x64": "4.
|
|
48
|
-
"sst-linux-x86": "4.
|
|
49
|
-
"sst-darwin-x64": "4.
|
|
50
|
-
"sst-linux-arm64": "4.
|
|
51
|
-
"sst-darwin-arm64": "4.
|
|
52
|
-
"sst-win32-x64": "4.
|
|
53
|
-
"sst-win32-x86": "4.
|
|
54
|
-
"sst-win32-arm64": "4.
|
|
48
|
+
"sst-linux-x64": "4.7.0",
|
|
49
|
+
"sst-linux-x86": "4.7.0",
|
|
50
|
+
"sst-darwin-x64": "4.7.0",
|
|
51
|
+
"sst-linux-arm64": "4.7.0",
|
|
52
|
+
"sst-darwin-arm64": "4.7.0",
|
|
53
|
+
"sst-win32-x64": "4.7.0",
|
|
54
|
+
"sst-win32-x86": "4.7.0",
|
|
55
|
+
"sst-win32-arm64": "4.7.0"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
58
|
+
"@aws/durable-execution-sdk-js": "1.0.2",
|
|
57
59
|
"aws4fetch": "1.0.18",
|
|
58
60
|
"jose": "5.2.3",
|
|
59
61
|
"openid-client": "5.6.4"
|