sst 2.27.0 → 2.28.1

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.
@@ -288,7 +288,7 @@ export async function useLocalServer(opts) {
288
288
  id: invocation.id,
289
289
  error: evt.properties.errorType,
290
290
  message: evt.properties.errorMessage,
291
- stack: evt.properties.trace.map((t) => ({
291
+ stack: (evt.properties.trace || []).map((t) => ({
292
292
  raw: t,
293
293
  })),
294
294
  });
@@ -11,6 +11,7 @@ export interface AstroSiteProps extends SsrSiteProps {
11
11
  * - "?" matches exactly 1 character.
12
12
  *
13
13
  * Matched routes will be handled directly by the server function.
14
+ * @deprecated Define `serverRoutes` in `astro.config.mjs` instead.
14
15
  * @default true
15
16
  * @example
16
17
  * ```js
@@ -25,17 +26,6 @@ export interface AstroSiteProps extends SsrSiteProps {
25
26
  * ```
26
27
  */
27
28
  serverRoutes?: string[];
28
- /**
29
- * Supports [streaming](https://docs.astro.build/en/guides/server-side-rendering/#using-streaming-to-improve-page-performance) responses.
30
- * @default true
31
- * @example
32
- * ```js
33
- * regional: {
34
- * streaming: false,
35
- * }
36
- * ```
37
- */
38
- streaming?: boolean;
39
29
  };
40
30
  }
41
31
  type AstroSiteNormalizedProps = AstroSiteProps & SsrSiteNormalizedProps;
@@ -1,8 +1,8 @@
1
1
  import { readFileSync, existsSync, readdirSync, statSync } from "fs";
2
2
  import { join } from "path";
3
- import { BUILD_META_FILE_NAME } from "astro-sst/build-meta";
4
3
  import { SsrSite, } from "./SsrSite.js";
5
4
  import { AllowedMethods } from "aws-cdk-lib/aws-cloudfront";
5
+ const BUILD_META_FILE_NAME = "sst.buildMeta.json";
6
6
  /**
7
7
  * The `AstroSite` construct is a higher level CDK construct that makes it easy to create a Astro app.
8
8
  * @example
@@ -40,7 +40,6 @@ export class AstroSite extends SsrSite {
40
40
  },
41
41
  ],
42
42
  regional: {
43
- streaming: props?.regional?.streaming ?? true,
44
43
  ...props?.regional,
45
44
  },
46
45
  });
@@ -158,7 +157,7 @@ export class AstroSite extends SsrSite {
158
157
  type: "function",
159
158
  constructId: "ServerFunction",
160
159
  function: serverConfig,
161
- streaming: regional?.streaming,
160
+ streaming: buildMeta.responseMode === "stream",
162
161
  };
163
162
  plan.origins.fallthroughServer = {
164
163
  type: "group",
@@ -175,7 +174,7 @@ export class AstroSite extends SsrSite {
175
174
  cacheType: "static",
176
175
  pattern: `${buildMeta.clientBuildVersionedSubDir}/*`,
177
176
  origin: "staticsServer",
178
- }, ...(regional?.serverRoutes ?? []).map((route) => ({
177
+ }, ...(buildMeta.serverRoutes ?? regional?.serverRoutes ?? []).map((route) => ({
179
178
  cacheType: "server",
180
179
  pattern: route,
181
180
  origin: "regionalServer",
@@ -270,7 +270,7 @@ export interface FunctionProps extends Omit<FunctionOptions, "functionName" | "m
270
270
  *
271
271
  * Note that, if a Layer is created in a stack (say `stackA`) and is referenced in another stack (say `stackB`), SST automatically creates an SSM parameter in `stackA` with the Layer's ARN. And in `stackB`, SST reads the ARN from the SSM parameter, and then imports the Layer.
272
272
  *
273
- * This is to get around the limitation that a Lambda Layer ARN cannot be referenced across stacks via a stack export. The Layer ARN contains a version number that is incremented everytime the Layer is modified. When you refer to a Layer's ARN across stacks, a CloudFormation export is created. However, CloudFormation does not allow an exported value to be updated. Once exported, if you try to deploy the updated layer, the CloudFormation update will fail. You can read more about this issue here - https://github.com/sst/sst/issues/549.
273
+ * This is to get around the limitation that a Lambda Layer ARN cannot be referenced across stacks via a stack export. The Layer ARN contains a version number that is incremented everytime the Layer is modified. When you refer to a Layer's ARN across stacks, a CloudFormation export is created. However, CloudFormation does not allow an exported value to be updated. Once exported, if you try to deploy the updated layer, the CloudFormation update will fail. You can read more about this issue here - https://github.com/sst/sst/issues/549.
274
274
  *
275
275
  * @default no layers
276
276
  *
@@ -282,6 +282,21 @@ export interface FunctionProps extends Omit<FunctionOptions, "functionName" | "m
282
282
  * ```
283
283
  */
284
284
  layers?: (string | ILayerVersion)[];
285
+ /**
286
+ * Disable sending function logs to CloudWatch Logs.
287
+ *
288
+ * Note that, logs will still appear locally when running `sst dev`.
289
+ * @default false
290
+ * @example
291
+ * ```js
292
+ * new Function(stack, "Function", {
293
+ * handler: "src/function.handler",
294
+ * disableCloudWatchLogs: true
295
+ * })
296
+ * ```
297
+ *
298
+ */
299
+ disableCloudWatchLogs?: boolean;
285
300
  /**
286
301
  * The duration function logs are kept in CloudWatch Logs.
287
302
  *
@@ -662,6 +677,7 @@ export declare class Function extends CDKFunction implements SSTConstruct {
662
677
  /** @internal */
663
678
  getFunctionBinding(): FunctionBindingProps;
664
679
  private createUrl;
680
+ private disableCloudWatchLogs;
665
681
  private isNodeRuntime;
666
682
  static validateHandlerSet(id: string, props: FunctionProps): void;
667
683
  static validateVpcSettings(id: string, props: FunctionProps): void;
@@ -294,7 +294,6 @@ export class Function extends CDKFunction {
294
294
  removeInEdge: true,
295
295
  });
296
296
  }
297
- // Attach permissions
298
297
  this.attachPermissions(props.permissions || []);
299
298
  // Add config
300
299
  this.addEnvironment("SST_APP", app.name, { removeInEdge: true });
@@ -303,6 +302,7 @@ export class Function extends CDKFunction {
303
302
  removeInEdge: true,
304
303
  });
305
304
  this.bind(props.bind || []);
305
+ this.disableCloudWatchLogs();
306
306
  this.createUrl();
307
307
  this._isLiveDevEnabled = isLiveDevEnabled;
308
308
  useFunctions().add(this.node.addr, props);
@@ -415,6 +415,22 @@ export class Function extends CDKFunction {
415
415
  cors: functionUrlCors.buildCorsConfig(cors),
416
416
  });
417
417
  }
418
+ disableCloudWatchLogs() {
419
+ const disableCloudWatchLogs = this.props.disableCloudWatchLogs ?? false;
420
+ if (!disableCloudWatchLogs)
421
+ return;
422
+ this.attachPermissions([
423
+ new PolicyStatement({
424
+ effect: Effect.DENY,
425
+ actions: [
426
+ "logs:CreateLogGroup",
427
+ "logs:CreateLogStream",
428
+ "logs:PutLogEvents",
429
+ ],
430
+ resources: ["*"],
431
+ }),
432
+ ]);
433
+ }
418
434
  isNodeRuntime() {
419
435
  const { runtime } = this.props;
420
436
  return runtime.startsWith("nodejs");
@@ -10,11 +10,49 @@ export interface NextjsSiteProps extends Omit<SsrSiteProps, "nodejs"> {
10
10
  * @default 1024 MB
11
11
  * @example
12
12
  * ```js
13
- * memorySize: "512 MB",
13
+ * imageOptimization: {
14
+ * memorySize: "512 MB",
15
+ * }
14
16
  * ```
15
17
  */
16
18
  memorySize?: number | Size;
17
19
  };
20
+ experimental?: {
21
+ /**
22
+ * Enable streaming. Currently an experimental feature in OpenNext.
23
+ * @default false
24
+ * @example
25
+ * ```js
26
+ * experimental: {
27
+ * streaming: true,
28
+ * }
29
+ * ```
30
+ */
31
+ streaming?: boolean;
32
+ /**
33
+ * Disabling incremental cache will cause the entire page to be revalidated on each request. This can result in ISR and SSG pages to be in an inconsistent state. Specify this option if you are using SSR pages only.
34
+ *
35
+ * Note that it is possible to disable incremental cache while leaving on-demand revalidation enabled.
36
+ * @default false
37
+ * @example
38
+ * ```js
39
+ * experimental: {
40
+ * disableIncrementalCache: true,
41
+ * }
42
+ */
43
+ disableIncrementalCache?: boolean;
44
+ /**
45
+ * Disabling DynamoDB cache will cause on-demand revalidation by path (`revalidatePath`) and by cache tag (`revalidateTag`) to fail silently.
46
+ * @default false
47
+ * @example
48
+ * ```js
49
+ * experimental: {
50
+ * disableDynamoDBCache: true,
51
+ * }
52
+ * ```
53
+ */
54
+ disableDynamoDBCache?: boolean;
55
+ };
18
56
  cdk?: SsrSiteProps["cdk"] & {
19
57
  revalidation?: Pick<FunctionProps, "vpc" | "vpcSubnets">;
20
58
  /**
@@ -129,6 +167,7 @@ export declare class NextjsSite extends SsrSite {
129
167
  CACHE_BUCKET_REGION: string;
130
168
  };
131
169
  };
170
+ streaming: boolean | undefined;
132
171
  } | undefined;
133
172
  };
134
173
  behaviors: {
@@ -147,7 +186,8 @@ export declare class NextjsSite extends SsrSite {
147
186
  schedule?: import("aws-cdk-lib/aws-events").Schedule | undefined;
148
187
  } | undefined;
149
188
  };
150
- protected createRevalidation(): void;
189
+ private createRevalidationQueue;
190
+ private createRevalidationTable;
151
191
  getConstructMetadata(): {
152
192
  data: {
153
193
  mode: "placeholder" | "deployed";
@@ -1,12 +1,16 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { Duration as CdkDuration } from "aws-cdk-lib/core";
3
+ import { Duration as CdkDuration, RemovalPolicy, CustomResource, } from "aws-cdk-lib/core";
4
4
  import { Code, Runtime, Function as CdkFunction, Architecture, } from "aws-cdk-lib/aws-lambda";
5
+ import { AttributeType, Billing, TableV2 as Table, } from "aws-cdk-lib/aws-dynamodb";
6
+ import { Provider } from "aws-cdk-lib/custom-resources";
5
7
  import { Queue } from "aws-cdk-lib/aws-sqs";
6
8
  import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
7
9
  import { Stack } from "./Stack.js";
8
10
  import { SsrSite } from "./SsrSite.js";
9
11
  import { toCdkSize } from "./util/size.js";
12
+ import { PolicyStatement } from "aws-cdk-lib/aws-iam";
13
+ import { RetentionDays } from "aws-cdk-lib/aws-logs";
10
14
  /**
11
15
  * The `NextjsSite` construct is a higher level CDK construct that makes it easy to create a Next.js app.
12
16
  * @example
@@ -20,14 +24,34 @@ import { toCdkSize } from "./util/size.js";
20
24
  */
21
25
  export class NextjsSite extends SsrSite {
22
26
  constructor(scope, id, props) {
27
+ const { streaming, disableDynamoDBCache, disableIncrementalCache } = {
28
+ streaming: false,
29
+ disableDynamoDBCache: false,
30
+ disableIncrementalCache: false,
31
+ ...props?.experimental,
32
+ };
23
33
  super(scope, id, {
24
- buildCommand: "npx --yes open-next@2.1.5 build",
34
+ buildCommand: [
35
+ "npx --yes open-next@2.2.1 build",
36
+ ...(streaming ? ["--streaming"] : []),
37
+ ...(disableDynamoDBCache
38
+ ? ["--dangerously-disable-dynamodb-cache"]
39
+ : []),
40
+ ...(disableIncrementalCache
41
+ ? ["--dangerously-disable-incremental-cache"]
42
+ : []),
43
+ ].join(" "),
25
44
  ...props,
26
45
  });
27
- this.createRevalidation();
46
+ if (!disableIncrementalCache) {
47
+ this.createRevalidationQueue();
48
+ if (!disableDynamoDBCache) {
49
+ this.createRevalidationTable();
50
+ }
51
+ }
28
52
  }
29
53
  plan(bucket) {
30
- const { path: sitePath, edge, imageOptimization } = this.props;
54
+ const { path: sitePath, edge, experimental, imageOptimization, } = this.props;
31
55
  const serverConfig = {
32
56
  description: "Next.js server",
33
57
  bundle: path.join(sitePath, ".open-next", "server-function"),
@@ -61,6 +85,7 @@ export class NextjsSite extends SsrSite {
61
85
  type: "function",
62
86
  constructId: "ServerFunction",
63
87
  function: serverConfig,
88
+ streaming: experimental?.streaming,
64
89
  },
65
90
  }),
66
91
  imageOptimizer: {
@@ -172,10 +197,11 @@ export class NextjsSite extends SsrSite {
172
197
  },
173
198
  });
174
199
  }
175
- createRevalidation() {
200
+ createRevalidationQueue() {
176
201
  if (!this.serverFunction)
177
202
  return;
178
203
  const { cdk } = this.props;
204
+ const server = this.serverFunction;
179
205
  const queue = new Queue(this, "RevalidationQueue", {
180
206
  fifo: true,
181
207
  receiveMessageWaitTime: CdkDuration.seconds(20),
@@ -190,10 +216,64 @@ export class NextjsSite extends SsrSite {
190
216
  });
191
217
  consumer.addEventSource(new SqsEventSource(queue, { batchSize: 5 }));
192
218
  // Allow server to send messages to the queue
219
+ server.addEnvironment("REVALIDATION_QUEUE_URL", queue.queueUrl);
220
+ server.addEnvironment("REVALIDATION_QUEUE_REGION", Stack.of(this).region);
221
+ queue.grantSendMessages(server.role);
222
+ }
223
+ createRevalidationTable() {
224
+ if (!this.serverFunction)
225
+ return;
226
+ const { path: sitePath } = this.props;
193
227
  const server = this.serverFunction;
194
- server?.addEnvironment("REVALIDATION_QUEUE_URL", queue.queueUrl);
195
- server?.addEnvironment("REVALIDATION_QUEUE_REGION", Stack.of(this).region);
196
- queue.grantSendMessages(server?.role);
228
+ const table = new Table(this, "RevalidationTable", {
229
+ partitionKey: { name: "tag", type: AttributeType.STRING },
230
+ sortKey: { name: "path", type: AttributeType.STRING },
231
+ pointInTimeRecovery: true,
232
+ billing: Billing.onDemand(),
233
+ globalSecondaryIndexes: [
234
+ {
235
+ indexName: "revalidate",
236
+ partitionKey: { name: "path", type: AttributeType.STRING },
237
+ sortKey: { name: "revalidatedAt", type: AttributeType.NUMBER },
238
+ },
239
+ ],
240
+ removalPolicy: RemovalPolicy.DESTROY,
241
+ });
242
+ server?.addEnvironment("CACHE_DYNAMO_TABLE", table.tableName);
243
+ table.grantReadWriteData(server.role);
244
+ const dynamodbProviderPath = path.join(sitePath, ".open-next", "dynamodb-provider");
245
+ if (fs.existsSync(dynamodbProviderPath)) {
246
+ const insertFn = new CdkFunction(this, "RevalidationInsertFunction", {
247
+ description: "Next.js revalidation data insert",
248
+ handler: "index.handler",
249
+ code: Code.fromAsset(dynamodbProviderPath),
250
+ runtime: Runtime.NODEJS_18_X,
251
+ timeout: CdkDuration.minutes(15),
252
+ initialPolicy: [
253
+ new PolicyStatement({
254
+ actions: [
255
+ "dynamodb:BatchWriteItem",
256
+ "dynamodb:PutItem",
257
+ "dynamodb:DescribeTable",
258
+ ],
259
+ resources: [table.tableArn],
260
+ }),
261
+ ],
262
+ environment: {
263
+ CACHE_DYNAMO_TABLE: table.tableName,
264
+ },
265
+ });
266
+ const provider = new Provider(this, "RevalidationProvider", {
267
+ onEventHandler: insertFn,
268
+ logRetention: RetentionDays.ONE_DAY,
269
+ });
270
+ new CustomResource(this, "RevalidationResource", {
271
+ serviceToken: provider.serviceToken,
272
+ properties: {
273
+ version: Date.now().toString(),
274
+ },
275
+ });
276
+ }
197
277
  }
198
278
  getConstructMetadata() {
199
279
  return {
@@ -1,4 +1,5 @@
1
1
  import { Construct } from "constructs";
2
+ import { DistributionProps } from "aws-cdk-lib/aws-cloudfront";
2
3
  import { DistributionDomainProps } from "./Distribution.js";
3
4
  import { SSTConstruct } from "./Construct.js";
4
5
  import { Permissions } from "./util/permission.js";
@@ -18,6 +19,8 @@ declare const supportedCpus: {
18
19
  };
19
20
  export interface ServiceDomainProps extends DistributionDomainProps {
20
21
  }
22
+ export interface ServiceCdkDistributionProps extends Omit<DistributionProps, "defaultBehavior"> {
23
+ }
21
24
  export interface ServiceProps {
22
25
  /**
23
26
  * Path to the directory where the app is located.
@@ -272,7 +275,7 @@ export interface ServiceProps {
272
275
  };
273
276
  cdk?: {
274
277
  /**
275
- * By default, SST creates a CloudFront distribution. Set this to `false` to skip creating the distribution.
278
+ * By default, SST creates a CloudFront distribution. Pass in a value to override the default settings this construct uses to create the CDK `Distribution` internally. Alternatively, set this to `false` to skip creating the distribution.
276
279
  * @default true
277
280
  * @example
278
281
  * ```js
@@ -283,7 +286,7 @@ export interface ServiceProps {
283
286
  * }
284
287
  * ```
285
288
  */
286
- cloudfrontDistribution?: boolean;
289
+ cloudfrontDistribution?: boolean | ServiceCdkDistributionProps;
287
290
  /**
288
291
  * By default, SST creates an Application Load Balancer to distribute requests across containers. Set this to `false` to skip creating the load balancer.
289
292
  * @default true
@@ -517,6 +517,9 @@ export class Service extends Construct {
517
517
  cachePolicy,
518
518
  originRequestPolicy: OriginRequestPolicy.ALL_VIEWER,
519
519
  },
520
+ ...(cdk?.cloudfrontDistribution === true
521
+ ? {}
522
+ : cdk?.cloudfrontDistribution),
520
523
  },
521
524
  },
522
525
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "sideEffects": false,
3
3
  "name": "sst",
4
- "version": "2.27.0",
4
+ "version": "2.28.1",
5
5
  "bin": {
6
6
  "sst": "cli/sst.js"
7
7
  },
@@ -119,7 +119,7 @@
119
119
  "@types/ws": "^8.5.3",
120
120
  "@types/yargs": "^17.0.13",
121
121
  "archiver": "^5.3.1",
122
- "astro-sst": "2.27.0",
122
+ "astro-sst": "2.28.1",
123
123
  "tsx": "^3.12.1",
124
124
  "typescript": "^5.2.2",
125
125
  "vitest": "^0.33.0"
@@ -35794,7 +35794,7 @@ var require_DeleteParameterCommand = __commonJS({
35794
35794
  return smithy_client_1.Command;
35795
35795
  } });
35796
35796
  var Aws_json1_1_1 = require_Aws_json1_1();
35797
- var DeleteParameterCommand2 = class _DeleteParameterCommand extends smithy_client_1.Command {
35797
+ var DeleteParameterCommand = class _DeleteParameterCommand extends smithy_client_1.Command {
35798
35798
  static getEndpointParameterInstructions() {
35799
35799
  return {
35800
35800
  UseFIPS: { type: "builtInParams", name: "useFipsEndpoint" },
@@ -35831,7 +35831,7 @@ var require_DeleteParameterCommand = __commonJS({
35831
35831
  return (0, Aws_json1_1_1.de_DeleteParameterCommand)(output, context);
35832
35832
  }
35833
35833
  };
35834
- exports.DeleteParameterCommand = DeleteParameterCommand2;
35834
+ exports.DeleteParameterCommand = DeleteParameterCommand;
35835
35835
  }
35836
35836
  });
35837
35837
 
@@ -35848,7 +35848,7 @@ var require_DeleteParametersCommand = __commonJS({
35848
35848
  return smithy_client_1.Command;
35849
35849
  } });
35850
35850
  var Aws_json1_1_1 = require_Aws_json1_1();
35851
- var DeleteParametersCommand = class _DeleteParametersCommand extends smithy_client_1.Command {
35851
+ var DeleteParametersCommand2 = class _DeleteParametersCommand extends smithy_client_1.Command {
35852
35852
  static getEndpointParameterInstructions() {
35853
35853
  return {
35854
35854
  UseFIPS: { type: "builtInParams", name: "useFipsEndpoint" },
@@ -35885,7 +35885,7 @@ var require_DeleteParametersCommand = __commonJS({
35885
35885
  return (0, Aws_json1_1_1.de_DeleteParametersCommand)(output, context);
35886
35886
  }
35887
35887
  };
35888
- exports.DeleteParametersCommand = DeleteParametersCommand;
35888
+ exports.DeleteParametersCommand = DeleteParametersCommand2;
35889
35889
  }
35890
35890
  });
35891
35891
 
@@ -179506,18 +179506,11 @@ async function AuthKeys(cfnRequest) {
179506
179506
  }
179507
179507
  break;
179508
179508
  case "Delete":
179509
- await Promise.all([
179510
- client.send(
179511
- new import_client_ssm.DeleteParameterCommand({
179512
- Name: privatePath
179513
- })
179514
- ),
179515
- client.send(
179516
- new import_client_ssm.DeleteParameterCommand({
179517
- Name: publicPath
179518
- })
179519
- )
179520
- ]);
179509
+ await client.send(
179510
+ new import_client_ssm.DeleteParametersCommand({
179511
+ Names: [publicPath, privatePath]
179512
+ })
179513
+ );
179521
179514
  break;
179522
179515
  default:
179523
179516
  throw new Error("Unsupported request type");