sst 2.11.0 → 2.11.2
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/cli/commands/bind.d.ts +4 -0
- package/cli/commands/bind.js +96 -86
- package/constructs/EventBus.d.ts +22 -0
- package/constructs/EventBus.js +55 -0
- package/constructs/NextjsSite.js +1 -1
- package/package.json +2 -1
- package/sst.mjs +90 -89
- package/support/event-bus-redriver/index.mjs +45 -0
package/cli/commands/bind.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export declare const bind: (program: Program) => import("yargs").Argv<{
|
|
|
12
12
|
role: string | undefined;
|
|
13
13
|
} & {
|
|
14
14
|
future: boolean | undefined;
|
|
15
|
+
} & {
|
|
16
|
+
site: boolean | undefined;
|
|
17
|
+
} & {
|
|
18
|
+
script: boolean | undefined;
|
|
15
19
|
} & {
|
|
16
20
|
command: (string | number)[] | undefined;
|
|
17
21
|
}>;
|
package/cli/commands/bind.js
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { VisibleError } from "../../error.js";
|
|
3
|
-
class
|
|
3
|
+
class MetadataNotFoundError extends Error {
|
|
4
|
+
}
|
|
5
|
+
class MetadataOutdatedError extends Error {
|
|
4
6
|
}
|
|
5
7
|
export const bind = (program) => program
|
|
6
8
|
.command(["bind <command..>", "env <command..>"], "Bind your app's resources to a command", (yargs) => yargs
|
|
9
|
+
.option("site", {
|
|
10
|
+
type: "boolean",
|
|
11
|
+
describe: "Run in site mode",
|
|
12
|
+
})
|
|
13
|
+
.option("script", {
|
|
14
|
+
type: "boolean",
|
|
15
|
+
describe: "Run in script mode",
|
|
16
|
+
})
|
|
7
17
|
.array("command")
|
|
8
|
-
.example(`sst bind
|
|
9
|
-
.example(`sst bind
|
|
18
|
+
.example(`sst bind vitest run`, "Bind resources to your tests")
|
|
19
|
+
.example(`sst bind next dev`, "Bind resources to your site")
|
|
20
|
+
.example(`sst bind --script next build`, "Bind resources to your site before deployment"), async (args) => {
|
|
10
21
|
const { spawn } = await import("child_process");
|
|
11
22
|
const kill = await import("tree-kill");
|
|
12
23
|
const { useProject } = await import("../../project.js");
|
|
@@ -22,6 +33,7 @@ export const bind = (program) => program
|
|
|
22
33
|
const project = useProject();
|
|
23
34
|
const command = args.command?.join(" ");
|
|
24
35
|
const isSite = await isRunningInSite();
|
|
36
|
+
const mode = args.site ? "site" : args.script ? "script" : "auto";
|
|
25
37
|
let p;
|
|
26
38
|
let timer;
|
|
27
39
|
let siteConfigCache;
|
|
@@ -30,22 +42,12 @@ export const bind = (program) => program
|
|
|
30
42
|
throw new VisibleError(`Command is required, e.g. sst bind ${isSite ? "next dev" : "vitest run"}`);
|
|
31
43
|
}
|
|
32
44
|
// Bind script
|
|
33
|
-
if (!isSite) {
|
|
45
|
+
if (args.script || (!isSite && !args.site)) {
|
|
34
46
|
Logger.debug("Running in script mode.");
|
|
35
47
|
return await bindScript();
|
|
36
48
|
}
|
|
37
49
|
// Bind site
|
|
38
|
-
|
|
39
|
-
await bindSite("init");
|
|
40
|
-
}
|
|
41
|
-
catch (e) {
|
|
42
|
-
// Bind script (fallback)
|
|
43
|
-
if (e instanceof OutdatedMetadataError) {
|
|
44
|
-
Colors.line(Colors.warning("Warning: This was deployed with an old version of SST. Run `sst dev` or `sst deploy` to update."));
|
|
45
|
-
return await bindScript();
|
|
46
|
-
}
|
|
47
|
-
throw e;
|
|
48
|
-
}
|
|
50
|
+
await bindSite("init");
|
|
49
51
|
bus.subscribe("stacks.metadata.updated", () => bindSite("metadata_updated"));
|
|
50
52
|
bus.subscribe("stacks.metadata.deleted", () => bindSite("metadata_updated"));
|
|
51
53
|
bus.subscribe("config.secret.updated", (payload) => {
|
|
@@ -93,7 +95,25 @@ export const bind = (program) => program
|
|
|
93
95
|
}
|
|
94
96
|
async function bindSite(reason) {
|
|
95
97
|
// Get metadata
|
|
96
|
-
|
|
98
|
+
let siteMetadata;
|
|
99
|
+
try {
|
|
100
|
+
siteMetadata = await getSiteMetadata();
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
// unhandled error
|
|
104
|
+
if (!(e instanceof MetadataOutdatedError) &&
|
|
105
|
+
!(e instanceof MetadataNotFoundError)) {
|
|
106
|
+
throw e;
|
|
107
|
+
}
|
|
108
|
+
// ignore error if previously failed to fetch metadata
|
|
109
|
+
if (reason !== "init")
|
|
110
|
+
return;
|
|
111
|
+
// run in script mode
|
|
112
|
+
Colors.line(Colors.warning(e instanceof MetadataOutdatedError
|
|
113
|
+
? "Warning: This was deployed with an old version of SST. Run `sst dev` or `sst deploy` to update."
|
|
114
|
+
: "Warning: The site has not been deployed. Some resources might not be available."));
|
|
115
|
+
return await bindScript();
|
|
116
|
+
}
|
|
97
117
|
const siteConfig = await parseSiteMetadata(siteMetadata);
|
|
98
118
|
// Handle rebind due to metadata updated
|
|
99
119
|
if (reason === "metadata_updated") {
|
|
@@ -103,57 +123,26 @@ export const bind = (program) => program
|
|
|
103
123
|
}
|
|
104
124
|
siteConfigCache = siteConfig;
|
|
105
125
|
// Assume function's role credentials
|
|
106
|
-
if (siteConfig.role) {
|
|
107
|
-
const credentials = await assumeSsrRole(siteConfig.role);
|
|
108
|
-
if (credentials) {
|
|
109
|
-
// refresh crecentials 1 minute before expiration
|
|
110
|
-
const expireAt = credentials.Expiration.getTime() - 60000;
|
|
111
|
-
clearTimeout(timer);
|
|
112
|
-
timer = setTimeout(() => {
|
|
113
|
-
Colors.line(`\n`, `Your AWS session is about to expire. Creating a new session and restarting \`${command}\`...`);
|
|
114
|
-
bindSite("iam_expired");
|
|
115
|
-
}, expireAt - Date.now());
|
|
116
|
-
await runCommand({
|
|
117
|
-
...siteConfig.envs,
|
|
118
|
-
AWS_ACCESS_KEY_ID: credentials.AccessKeyId,
|
|
119
|
-
AWS_SECRET_ACCESS_KEY: credentials.SecretAccessKey,
|
|
120
|
-
AWS_SESSION_TOKEN: credentials.SessionToken,
|
|
121
|
-
});
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
126
|
// Fallback to use local IAM credentials
|
|
127
|
+
const credentials = (siteConfig.role &&
|
|
128
|
+
(await getLiveIamCredentials(siteConfig.role))) ||
|
|
129
|
+
(await getLocalIamCredentials());
|
|
126
130
|
await runCommand({
|
|
127
131
|
...siteConfig.envs,
|
|
128
|
-
...
|
|
132
|
+
...credentials,
|
|
129
133
|
});
|
|
130
134
|
}
|
|
131
135
|
async function bindScript() {
|
|
132
136
|
const { Config } = await import("../../config.js");
|
|
133
137
|
await runCommand({
|
|
134
138
|
...(await Config.env()),
|
|
135
|
-
...(await
|
|
139
|
+
...(await getLocalIamCredentials()),
|
|
136
140
|
});
|
|
137
141
|
}
|
|
138
|
-
async function getSiteMetadataUntilAvailable() {
|
|
139
|
-
const { createSpinner } = await import("../spinner.js");
|
|
140
|
-
const spinner = createSpinner({});
|
|
141
|
-
while (true) {
|
|
142
|
-
const data = await getSiteMetadata();
|
|
143
|
-
// Handle site metadata not found
|
|
144
|
-
if (!data) {
|
|
145
|
-
spinner.start("Make sure `sst dev` is running...");
|
|
146
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
spinner.isSpinning && spinner.stop().clear();
|
|
150
|
-
return data;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
142
|
async function getSiteMetadata() {
|
|
154
143
|
const { metadata } = await import("../../stacks/metadata.js");
|
|
155
144
|
const metadataData = await metadata();
|
|
156
|
-
|
|
145
|
+
const data = Object.values(metadataData)
|
|
157
146
|
.flat()
|
|
158
147
|
.filter((c) => [
|
|
159
148
|
"StaticSite",
|
|
@@ -170,10 +159,14 @@ export const bind = (program) => program
|
|
|
170
159
|
if (!c.data.path ||
|
|
171
160
|
(isSsr && !c.data.server) ||
|
|
172
161
|
(!isSsr && !c.data.environment)) {
|
|
173
|
-
throw new
|
|
162
|
+
throw new MetadataOutdatedError();
|
|
174
163
|
}
|
|
175
164
|
return (path.resolve(project.paths.root, c.data.path) === process.cwd());
|
|
176
165
|
});
|
|
166
|
+
if (!data) {
|
|
167
|
+
throw new MetadataNotFoundError();
|
|
168
|
+
}
|
|
169
|
+
return data;
|
|
177
170
|
}
|
|
178
171
|
async function parseSiteMetadata(metadata) {
|
|
179
172
|
const { LambdaClient, GetFunctionCommand } = await import("@aws-sdk/client-lambda");
|
|
@@ -194,40 +187,24 @@ export const bind = (program) => program
|
|
|
194
187
|
secrets: metadata.data.secrets,
|
|
195
188
|
};
|
|
196
189
|
}
|
|
197
|
-
async function
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
190
|
+
async function getLiveIamCredentials(roleArn) {
|
|
191
|
+
const credentials = await assumeSsrRole(roleArn);
|
|
192
|
+
if (!credentials)
|
|
193
|
+
return;
|
|
194
|
+
// refresh crecentials 1 minute before expiration
|
|
195
|
+
const expireAt = credentials.Expiration.getTime() - 60000;
|
|
196
|
+
clearTimeout(timer);
|
|
197
|
+
timer = setTimeout(() => {
|
|
198
|
+
Colors.line(`\n`, `Your AWS session is about to expire. Creating a new session and restarting \`${command}\`...`);
|
|
199
|
+
bindSite("iam_expired");
|
|
200
|
+
}, expireAt - Date.now());
|
|
201
|
+
return {
|
|
202
|
+
AWS_ACCESS_KEY_ID: credentials.AccessKeyId,
|
|
203
|
+
AWS_SECRET_ACCESS_KEY: credentials.SecretAccessKey,
|
|
204
|
+
AWS_SESSION_TOKEN: credentials.SessionToken,
|
|
208
205
|
};
|
|
209
|
-
// Assue role with max duration first. This can fail if chaining roles, or if
|
|
210
|
-
// the role has a max duration set. If it fails, assume role with 1 hour duration.
|
|
211
|
-
let err;
|
|
212
|
-
try {
|
|
213
|
-
return await assumeRole(43200);
|
|
214
|
-
}
|
|
215
|
-
catch (e) {
|
|
216
|
-
err = e;
|
|
217
|
-
}
|
|
218
|
-
if (err.name === "ValidationError" &&
|
|
219
|
-
err.message.startsWith("The requested DurationSeconds exceeds")) {
|
|
220
|
-
try {
|
|
221
|
-
return await assumeRole(3600);
|
|
222
|
-
}
|
|
223
|
-
catch (e) {
|
|
224
|
-
err = e;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
Colors.line("Using local IAM credentials since `sst dev` is not running.");
|
|
228
|
-
Logger.debug(`Failed to assume ${roleArn}.`, err);
|
|
229
206
|
}
|
|
230
|
-
async function
|
|
207
|
+
async function getLocalIamCredentials() {
|
|
231
208
|
const { useAWSCredentials } = await import("../../credentials.js");
|
|
232
209
|
const credentials = await useAWSCredentials();
|
|
233
210
|
return {
|
|
@@ -269,5 +246,38 @@ export const bind = (program) => program
|
|
|
269
246
|
return (Object.keys(envs1).length === Object.keys(envs2).length &&
|
|
270
247
|
Object.keys(envs1).every((key) => envs1[key] === envs2[key]));
|
|
271
248
|
}
|
|
249
|
+
async function assumeSsrRole(roleArn) {
|
|
250
|
+
const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
|
|
251
|
+
const { useAWSClient } = await import("../../credentials.js");
|
|
252
|
+
const sts = useAWSClient(STSClient);
|
|
253
|
+
const assumeRole = async (duration) => {
|
|
254
|
+
const { Credentials: credentials } = await sts.send(new AssumeRoleCommand({
|
|
255
|
+
RoleArn: roleArn,
|
|
256
|
+
RoleSessionName: "dev-session",
|
|
257
|
+
DurationSeconds: duration,
|
|
258
|
+
}));
|
|
259
|
+
return credentials;
|
|
260
|
+
};
|
|
261
|
+
// Assue role with max duration first. This can fail if chaining roles, or if
|
|
262
|
+
// the role has a max duration set. If it fails, assume role with 1 hour duration.
|
|
263
|
+
let err;
|
|
264
|
+
try {
|
|
265
|
+
return await assumeRole(43200);
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
err = e;
|
|
269
|
+
}
|
|
270
|
+
if (err.name === "ValidationError" &&
|
|
271
|
+
err.message.startsWith("The requested DurationSeconds exceeds")) {
|
|
272
|
+
try {
|
|
273
|
+
return await assumeRole(3600);
|
|
274
|
+
}
|
|
275
|
+
catch (e) {
|
|
276
|
+
err = e;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
Colors.line("Using local IAM credentials since `sst dev` is not running.");
|
|
280
|
+
Logger.debug(`Failed to assume ${roleArn}.`, err);
|
|
281
|
+
}
|
|
272
282
|
})
|
|
273
283
|
.strict(false);
|
package/constructs/EventBus.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ export interface EventBusFunctionTargetProps {
|
|
|
32
32
|
* The function to trigger
|
|
33
33
|
*/
|
|
34
34
|
function?: FunctionDefinition;
|
|
35
|
+
/**
|
|
36
|
+
* Number of retries
|
|
37
|
+
*/
|
|
38
|
+
retries?: number;
|
|
35
39
|
cdk?: {
|
|
36
40
|
function?: lambda.IFunction;
|
|
37
41
|
target?: LambdaFunctionTargetProps;
|
|
@@ -212,6 +216,17 @@ export interface EventBusProps {
|
|
|
212
216
|
* ```
|
|
213
217
|
*/
|
|
214
218
|
function?: FunctionProps;
|
|
219
|
+
/**
|
|
220
|
+
* Enable retries with exponential backoff for all lambda function targets in this eventbus
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```js
|
|
224
|
+
* new EventBus(stack, "Bus", {
|
|
225
|
+
* retries: 20
|
|
226
|
+
* });
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
retries?: number;
|
|
215
230
|
};
|
|
216
231
|
/**
|
|
217
232
|
* The rules for the eventbus
|
|
@@ -409,11 +424,18 @@ export declare class EventBus extends Construct implements SSTConstruct {
|
|
|
409
424
|
};
|
|
410
425
|
/** @internal */
|
|
411
426
|
getFunctionBinding(): FunctionBindingProps;
|
|
427
|
+
private retrierQueue;
|
|
428
|
+
private retrierFn;
|
|
429
|
+
private getRetrier;
|
|
412
430
|
private createEventBus;
|
|
413
431
|
private addRule;
|
|
414
432
|
private addTarget;
|
|
415
433
|
private addQueueTarget;
|
|
416
434
|
private addLogGroupTarget;
|
|
417
435
|
private addCdkFunctionTarget;
|
|
436
|
+
private subs;
|
|
437
|
+
subscribe(type: string, target: FunctionDefinition, props?: {
|
|
438
|
+
retries?: number;
|
|
439
|
+
}): this;
|
|
418
440
|
private addFunctionTarget;
|
|
419
441
|
}
|
package/constructs/EventBus.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { Construct } from "constructs";
|
|
2
|
+
import * as lambda from "aws-cdk-lib/aws-lambda";
|
|
3
|
+
import * as sqs from "aws-cdk-lib/aws-sqs";
|
|
2
4
|
import * as events from "aws-cdk-lib/aws-events";
|
|
3
5
|
import { LambdaFunction as LambdaFunctionTarget, SqsQueue as SqsQueueTarget, CloudWatchLogGroup as LogGroupTarget, } from "aws-cdk-lib/aws-events-targets";
|
|
4
6
|
import { Queue } from "./Queue.js";
|
|
5
7
|
import { getFunctionRef, isCDKConstruct } from "./Construct.js";
|
|
6
8
|
import { Function as Fn, } from "./Function.js";
|
|
9
|
+
import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
|
10
|
+
import { SqsDestination } from "aws-cdk-lib/aws-lambda-destinations";
|
|
11
|
+
import path from "path";
|
|
7
12
|
/////////////////////
|
|
8
13
|
// Construct
|
|
9
14
|
/////////////////////
|
|
@@ -224,6 +229,25 @@ export class EventBus extends Construct {
|
|
|
224
229
|
},
|
|
225
230
|
};
|
|
226
231
|
}
|
|
232
|
+
retrierQueue;
|
|
233
|
+
retrierFn;
|
|
234
|
+
getRetrier() {
|
|
235
|
+
if (this.retrierFn && this.retrierQueue) {
|
|
236
|
+
return { fn: this.retrierFn, queue: this.retrierQueue };
|
|
237
|
+
}
|
|
238
|
+
this.retrierQueue = new sqs.Queue(this, `RetrierQueue`);
|
|
239
|
+
this.retrierFn = new lambda.Function(this, `RetrierFunction`, {
|
|
240
|
+
runtime: lambda.Runtime.NODEJS_14_X,
|
|
241
|
+
handler: "index.handler",
|
|
242
|
+
code: lambda.Code.fromAsset(path.join(__dirname, "../support/event-bus-retrier")),
|
|
243
|
+
environment: {
|
|
244
|
+
RETRIER_QUEUE_URL: this.retrierQueue.queueUrl,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
this.retrierFn.addEventSource(new SqsEventSource(this.retrierQueue));
|
|
248
|
+
this.retrierQueue.grantSendMessages(this.retrierFn);
|
|
249
|
+
return { fn: this.retrierFn, queue: this.retrierQueue };
|
|
250
|
+
}
|
|
227
251
|
createEventBus() {
|
|
228
252
|
const app = this.node.root;
|
|
229
253
|
const id = this.node.id;
|
|
@@ -321,14 +345,36 @@ export class EventBus extends Construct {
|
|
|
321
345
|
// Create target
|
|
322
346
|
eventsRule.addTarget(new LambdaFunctionTarget(fn, targetProps));
|
|
323
347
|
}
|
|
348
|
+
subs = new Map();
|
|
349
|
+
subscribe(type, target, props) {
|
|
350
|
+
const count = this.subs.get(type) || 0 + 1;
|
|
351
|
+
this.subs.set(type, count);
|
|
352
|
+
const name = `${type.replaceAll(/[^a-zA-Z_]/g, "_")}_${count}`;
|
|
353
|
+
const fn = Fn.fromDefinition(this, name, target);
|
|
354
|
+
this.addRule(this, name + "_rule", {
|
|
355
|
+
pattern: {
|
|
356
|
+
detailType: [type],
|
|
357
|
+
},
|
|
358
|
+
targets: {
|
|
359
|
+
[name + "_target"]: {
|
|
360
|
+
type: "function",
|
|
361
|
+
function: fn,
|
|
362
|
+
retries: props?.retries,
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
return this;
|
|
367
|
+
}
|
|
324
368
|
addFunctionTarget(scope, ruleKey, eventsRule, targetName, target) {
|
|
325
369
|
// Parse target props
|
|
326
370
|
let targetProps;
|
|
327
371
|
let functionDefinition;
|
|
372
|
+
let retries = this.props.defaults?.retries;
|
|
328
373
|
if (target.function) {
|
|
329
374
|
target = target;
|
|
330
375
|
targetProps = target.cdk?.target;
|
|
331
376
|
functionDefinition = target.function;
|
|
377
|
+
retries = target.retries;
|
|
332
378
|
}
|
|
333
379
|
else {
|
|
334
380
|
target = target;
|
|
@@ -339,6 +385,15 @@ export class EventBus extends Construct {
|
|
|
339
385
|
this.targetsData[ruleKey][targetName] = fn;
|
|
340
386
|
// Create target
|
|
341
387
|
eventsRule.addTarget(new LambdaFunctionTarget(fn, targetProps));
|
|
388
|
+
// Configure retrier
|
|
389
|
+
if (retries) {
|
|
390
|
+
const retrier = this.getRetrier();
|
|
391
|
+
retrier.fn.addEnvironment(`RETRIER_${retrier.fn.functionName}`, retries.toString());
|
|
392
|
+
fn.grantInvoke(retrier.fn);
|
|
393
|
+
fn.configureAsyncInvoke({
|
|
394
|
+
onFailure: new SqsDestination(retrier.queue),
|
|
395
|
+
});
|
|
396
|
+
}
|
|
342
397
|
// Attach existing permissions
|
|
343
398
|
this.permissionsAttachedForAllTargets.forEach((permissions) => fn.attachPermissions(permissions));
|
|
344
399
|
fn.bind(this.bindingForAllTargets);
|
package/constructs/NextjsSite.js
CHANGED
|
@@ -29,7 +29,7 @@ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
|
|
|
29
29
|
export class NextjsSite extends SsrSite {
|
|
30
30
|
constructor(scope, id, props) {
|
|
31
31
|
super(scope, id, {
|
|
32
|
-
buildCommand: "npx --yes open-next
|
|
32
|
+
buildCommand: "npx --yes open-next@^1.3 build",
|
|
33
33
|
...props,
|
|
34
34
|
});
|
|
35
35
|
this.createWarmer();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sideEffects": false,
|
|
3
3
|
"name": "sst",
|
|
4
|
-
"version": "2.11.
|
|
4
|
+
"version": "2.11.2",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sst": "cli/sst.js"
|
|
7
7
|
},
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"@aws-sdk/client-api-gateway": "^3.208.0",
|
|
94
94
|
"@aws-sdk/client-cloudfront": "^3.279.0",
|
|
95
95
|
"@aws-sdk/client-codebuild": "^3.279.0",
|
|
96
|
+
"@aws-sdk/client-sqs": "^3.341.0",
|
|
96
97
|
"@aws-sdk/types": "^3.272.0",
|
|
97
98
|
"@graphql-tools/merge": "^8.3.16",
|
|
98
99
|
"@sls-next/lambda-at-edge": "^3.7.0",
|