sst 4.6.11 → 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 CHANGED
@@ -1,4 +1,4 @@
1
- import { AwsOptions } from "../aws/client.js";
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?: AwsOptions;
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 { client } from "../aws/client.js";
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 c
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);
@@ -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 = Exclude<Parameters<AwsClient["fetch"]>[1], null | undefined>["aws"];
11
+ export type AwsOptions = aws.Options;
12
+ export {};
@@ -13,24 +13,40 @@ async function getCredentials(url) {
13
13
  cachedCredentials = credentials;
14
14
  return credentials;
15
15
  }
16
- export async function client() {
17
- if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
18
- return new AwsClient({
19
- accessKeyId: process.env.AWS_ACCESS_KEY_ID,
20
- secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
21
- sessionToken: process.env.AWS_SESSION_TOKEN,
22
- region: process.env.AWS_REGION,
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
- if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
26
- const credentials = await getCredentials("http://169.254.170.2" +
27
- process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI);
28
- return new AwsClient({
29
- accessKeyId: credentials.AccessKeyId,
30
- secretAccessKey: credentials.SecretAccessKey,
31
- sessionToken: credentials.Token,
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
- throw new Error("No AWS credentials found");
48
+ aws.fetch = fetch;
49
+ })(aws || (aws = {}));
50
+ export async function client() {
51
+ return aws.client();
36
52
  }
@@ -1,4 +1,4 @@
1
- import { AwsOptions } from "./client.js";
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?: AwsOptions;
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 { client } from "./client.js";
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 c = await client();
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 c = await client();
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 c = await client();
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
+ }
@@ -1,5 +1,5 @@
1
1
  import { Resource } from "../resource.js";
2
- import { client } from "../aws/client.js";
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.11",
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.6.11",
48
- "sst-linux-x86": "4.6.11",
49
- "sst-darwin-x64": "4.6.11",
50
- "sst-linux-arm64": "4.6.11",
51
- "sst-darwin-arm64": "4.6.11",
52
- "sst-win32-x64": "4.6.11",
53
- "sst-win32-x86": "4.6.11",
54
- "sst-win32-arm64": "4.6.11"
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"