@ts-cloud/core 0.5.4 → 0.5.6

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/index.js CHANGED
@@ -45416,16 +45416,21 @@ function resolveServerlessRuntime(app) {
45416
45416
  }
45417
45417
 
45418
45418
  // src/serverless/composer.ts
45419
- function resolveQueueNames(app, slug, env) {
45419
+ function resolveQueues(app, slug, env) {
45420
45420
  if (app.queues === false)
45421
45421
  return [];
45422
45422
  if (app.queues === undefined || app.queues === true)
45423
- return [`${slug}-${env}-default`];
45423
+ return [{ name: `${slug}-${env}-default` }];
45424
45424
  return app.queues.map((q) => {
45425
- const name = typeof q === "string" ? q : Object.keys(q)[0];
45426
- return `${slug}-${env}-${name}`;
45425
+ if (typeof q === "string")
45426
+ return { name: `${slug}-${env}-${q}` };
45427
+ const [name, concurrency] = Object.entries(q)[0];
45428
+ return { name: `${slug}-${env}-${name}`, concurrency };
45427
45429
  });
45428
45430
  }
45431
+ function resolveQueueNames(app, slug, env) {
45432
+ return resolveQueues(app, slug, env).map((q) => q.name);
45433
+ }
45429
45434
  function composeServerlessAppTemplate(opts) {
45430
45435
  const { app, environment, handlers } = opts;
45431
45436
  const slug = opts.config.project.slug;
@@ -45437,8 +45442,12 @@ function composeServerlessAppTemplate(opts) {
45437
45442
  queue: `${slug}-${environment}-queue`,
45438
45443
  cli: `${slug}-${environment}-cli`
45439
45444
  };
45440
- const queueNames = resolveQueueNames(app, slug, environment);
45445
+ if (app.gatewayVersion === 1)
45446
+ throw new Error("serverless app: `gatewayVersion: 1` (REST API) is not supported — ts-cloud uses API Gateway HTTP API (v2). Remove `gatewayVersion` or set it to 2.");
45447
+ const queues = resolveQueues(app, slug, environment);
45448
+ const queueNames = queues.map((q) => q.name);
45441
45449
  const hasQueue = queueNames.length > 0;
45450
+ const logRetention = app.logRetention ?? 14;
45442
45451
  const imageMode = app.packaging === "image";
45443
45452
  const schedulerEnabled = (app.scheduler ?? "on") !== "off";
45444
45453
  const cacheEnabled = (app.cache?.driver ?? "dynamodb") === "dynamodb";
@@ -45554,7 +45563,7 @@ function composeServerlessAppTemplate(opts) {
45554
45563
  function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency, tmp = tmpStorage) {
45555
45564
  resources[`${logicalId}LogGroup`] = {
45556
45565
  Type: "AWS::Logs::LogGroup",
45557
- Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: 14 }
45566
+ Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: logRetention }
45558
45567
  };
45559
45568
  const codeProps = imageMode ? {
45560
45569
  PackageType: "Image",
@@ -45695,19 +45704,21 @@ function composeServerlessAppTemplate(opts) {
45695
45704
  MessageRetentionPeriod: 1209600
45696
45705
  }
45697
45706
  };
45698
- queueNames.forEach((qName, i) => {
45707
+ queues.forEach((q, i) => {
45699
45708
  const qId = `AppQueue${i}`;
45709
+ const fnTimeout = app.queueTimeout ?? 120;
45700
45710
  resources[qId] = {
45701
45711
  Type: "AWS::SQS::Queue",
45702
45712
  Properties: {
45703
- QueueName: qName,
45704
- VisibilityTimeout: Math.max(app.queueTimeout ?? 120, app.queueTimeout ?? 120),
45713
+ QueueName: q.name,
45714
+ VisibilityTimeout: Math.min(43200, fnTimeout * 6),
45705
45715
  RedrivePolicy: {
45706
45716
  deadLetterTargetArn: Fn2.getAtt("AppQueueDlq", "Arn"),
45707
45717
  maxReceiveCount: app.queueTries ?? 3
45708
45718
  }
45709
45719
  }
45710
45720
  };
45721
+ const concurrency = q.concurrency ?? app.queueConcurrency;
45711
45722
  resources[`${qId}Mapping`] = {
45712
45723
  Type: "AWS::Lambda::EventSourceMapping",
45713
45724
  Properties: {
@@ -45715,10 +45726,10 @@ function composeServerlessAppTemplate(opts) {
45715
45726
  FunctionName: Fn2.ref("QueueFunction"),
45716
45727
  BatchSize: 1,
45717
45728
  FunctionResponseTypes: ["ReportBatchItemFailures"],
45718
- ...app.queueConcurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, app.queueConcurrency) } } : {}
45729
+ ...concurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, concurrency) } } : {}
45719
45730
  }
45720
45731
  };
45721
- outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${qName}`, Value: Fn2.ref(qId) };
45732
+ outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${q.name}`, Value: Fn2.ref(qId) };
45722
45733
  });
45723
45734
  }
45724
45735
  if (schedulerEnabled) {
@@ -45833,7 +45844,7 @@ function composeServerlessAppTemplate(opts) {
45833
45844
  ...app.assetDomain ? {
45834
45845
  Aliases: [app.assetDomain],
45835
45846
  ViewerCertificate: {
45836
- AcmCertificateArn: app.assetCertificateArn,
45847
+ AcmCertificateArn: app.assetCertificateArn ?? Fn2.ref("AssetsCertificate"),
45837
45848
  SslSupportMethod: "sni-only",
45838
45849
  MinimumProtocolVersion: "TLSv1.2_2021"
45839
45850
  }
@@ -45842,8 +45853,20 @@ function composeServerlessAppTemplate(opts) {
45842
45853
  }
45843
45854
  };
45844
45855
  if (app.assetDomain) {
45845
- if (!app.assetCertificateArn)
45846
- throw new Error("serverless app: `assetDomain` requires `assetCertificateArn` (a us-east-1 ACM cert — CloudFront only accepts certs from us-east-1).");
45856
+ if (!app.assetCertificateArn) {
45857
+ if (!app.hostedZoneId)
45858
+ throw new Error("serverless app: `assetDomain` requires either `assetCertificateArn` (a us-east-1 ACM cert) or `hostedZoneId` (to auto-issue + validate one). CloudFront only accepts certs from us-east-1.");
45859
+ if (region !== "us-east-1")
45860
+ throw new Error(`serverless app: auto-issuing an asset-domain cert needs a us-east-1 app (CloudFront certs must be us-east-1); this app is in ${region}. Supply a pre-issued us-east-1 \`assetCertificateArn\` instead.`);
45861
+ resources.AssetsCertificate = {
45862
+ Type: "AWS::CertificateManager::Certificate",
45863
+ Properties: {
45864
+ DomainName: app.assetDomain,
45865
+ ValidationMethod: "DNS",
45866
+ DomainValidationOptions: [{ DomainName: app.assetDomain, HostedZoneId: app.hostedZoneId }]
45867
+ }
45868
+ };
45869
+ }
45847
45870
  if (app.hostedZoneId) {
45848
45871
  resources.AssetsDomainRecord = {
45849
45872
  Type: "AWS::Route53::RecordSet",
@@ -47099,6 +47122,7 @@ export {
47099
47122
  resolveServerlessArtifactBucketName,
47100
47123
  resolveServerlessAppStackName,
47101
47124
  resolveRegion,
47125
+ resolveQueues,
47102
47126
  resolveQueueNames,
47103
47127
  resolveProjectStackName,
47104
47128
  resolveManagementDashboardSite,
@@ -17,9 +17,6 @@ require $taskRoot . '/vendor/autoload.php';
17
17
  $app = require $taskRoot . '/bootstrap/app.php';
18
18
  $kernel = $app->make(\Illuminate\Contracts\Http\Kernel::class);
19
19
 
20
- $maintenance = getenv('MAINTENANCE_MODE') === '1';
21
- $bypassSecret = getenv('MAINTENANCE_BYPASS_SECRET') ?: '';
22
-
23
20
  while (true) {
24
21
  $ctx = nextInvocation($runtimeApi);
25
22
  if ($ctx === null) {
@@ -27,6 +24,11 @@ while (true) {
27
24
  }
28
25
  [$requestId, $event] = $ctx;
29
26
 
27
+ // Read maintenance state per-invocation so `cloud down`/`up` reaches warm
28
+ // containers (env is flipped via updateFunctionConfiguration).
29
+ $maintenance = getenv('MAINTENANCE_MODE') === '1';
30
+ $bypassSecret = getenv('MAINTENANCE_BYPASS_SECRET') ?: '';
31
+
30
32
  try {
31
33
  $response = handle($event, $kernel, $maintenance, $bypassSecret);
32
34
  postResponse($runtimeApi, $requestId, $response);
@@ -24,9 +24,6 @@ for ($i = 0; $i < 50 && !file_exists($socketPath); $i++) {
24
24
  usleep(100000); // 100ms
25
25
  }
26
26
 
27
- $maintenance = getenv('MAINTENANCE_MODE') === '1';
28
- $bypassSecret = getenv('MAINTENANCE_BYPASS_SECRET') ?: '';
29
-
30
27
  while (true) {
31
28
  // 1. Get the next invocation.
32
29
  $ctx = nextInvocation($runtimeApi);
@@ -35,6 +32,12 @@ while (true) {
35
32
  }
36
33
  [$requestId, $event] = $ctx;
37
34
 
35
+ // Read maintenance state per-invocation: `cloud down`/`up` flips the env via
36
+ // updateFunctionConfiguration, but warm containers would keep a cold-start
37
+ // value if cached outside the loop (the Node adapter reads it per-request too).
38
+ $maintenance = getenv('MAINTENANCE_MODE') === '1';
39
+ $bypassSecret = getenv('MAINTENANCE_BYPASS_SECRET') ?: '';
40
+
38
41
  try {
39
42
  $response = handle($event, $fpm, $docRoot, $maintenance, $bypassSecret);
40
43
  postResponse($runtimeApi, $requestId, $response);
@@ -38,6 +38,17 @@ export interface ComposedTemplate {
38
38
  /** Count of resources by CloudFormation type (for deploy summaries). */
39
39
  resourceSummary: Record<string, number>;
40
40
  }
41
- /** Resolve the SQS queue names from the manifest. */
41
+ /** A resolved queue: its full name plus an optional per-queue concurrency cap. */
42
+ export interface ResolvedQueue {
43
+ name: string;
44
+ /** Per-queue max concurrency (`queues: [{ emails: 10 }]`), if specified. */
45
+ concurrency?: number;
46
+ }
47
+ /**
48
+ * Resolve the SQS queues from the manifest, preserving any per-queue concurrency
49
+ * (`queues: [{ emails: 10 }]` → `{ name: '…-emails', concurrency: 10 }`).
50
+ */
51
+ export declare function resolveQueues(app: ServerlessAppConfig, slug: string, env: EnvironmentType): ResolvedQueue[];
52
+ /** Resolve just the SQS queue names from the manifest. */
42
53
  export declare function resolveQueueNames(app: ServerlessAppConfig, slug: string, env: EnvironmentType): string[];
43
54
  export declare function composeServerlessAppTemplate(opts: ComposeOptions): ComposedTemplate;
package/dist/types.d.ts CHANGED
@@ -1579,13 +1579,19 @@ export interface ServerlessAppConfig {
1579
1579
  timeout?: number;
1580
1580
  /** Reserved concurrency for the HTTP function. */
1581
1581
  concurrency?: number;
1582
- /** API Gateway version: 2 = HTTP API (cheaper, default), 1 = REST API. @default 2 */
1582
+ /**
1583
+ * API Gateway version. Only `2` (HTTP API — cheaper, faster) is supported;
1584
+ * `1` (REST API) throws at compose time. @default 2
1585
+ */
1583
1586
  gatewayVersion?: 1 | 2;
1584
1587
  /**
1585
- * Keep-warm count. Sets provisioned concurrency on the HTTP alias, or drives a
1586
- * scheduled warmer rule. 0/undefined disables warming.
1588
+ * Keep-warm count. Drives a scheduled EventBridge warmer rule that pings the
1589
+ * HTTP function every few minutes (the runtime short-circuits warmer pings).
1590
+ * 0/undefined disables warming.
1587
1591
  */
1588
1592
  warm?: number;
1593
+ /** CloudWatch log retention (days) for all function log groups. @default 14 */
1594
+ logRetention?: number;
1589
1595
  /** CLI function memory in MB. @default 1024 */
1590
1596
  cliMemory?: number;
1591
1597
  /** CLI command timeout in seconds (allow room for migrations). @default 900 */
@@ -1692,10 +1698,16 @@ export interface ServerlessAppConfig {
1692
1698
  assets?: string;
1693
1699
  /**
1694
1700
  * Serve assets from a custom CDN host instead of the default CloudFront domain
1695
- * (Vapor `asset-domain`). Requires a us-east-1 ACM cert via assetCertificateArn.
1701
+ * (Vapor `asset-domain`). CloudFront needs a us-east-1 ACM cert: supply one via
1702
+ * {@link assetCertificateArn}, or give {@link hostedZoneId} and (for a us-east-1
1703
+ * app) ts-cloud auto-issues + DNS-validates one.
1696
1704
  */
1697
1705
  assetDomain?: string;
1698
- /** us-east-1 ACM certificate ARN for {@link assetDomain} (CloudFront requirement). */
1706
+ /**
1707
+ * us-east-1 ACM certificate ARN for {@link assetDomain} (CloudFront requirement).
1708
+ * Optional when {@link hostedZoneId} is set and the app is in us-east-1 — the
1709
+ * cert is then auto-issued and DNS-validated.
1710
+ */
1699
1711
  assetCertificateArn?: string;
1700
1712
  /** Include dotfiles when uploading assets (Vapor `dot-files-as-assets`). @default false */
1701
1713
  dotFilesAsAssets?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-cloud/core",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "type": "module",
5
5
  "description": "Core CloudFormation generation library for ts-cloud",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
@@ -31,7 +31,7 @@
31
31
  "typecheck": "tsc --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@ts-cloud/aws-types": "0.5.4"
34
+ "@ts-cloud/aws-types": "0.5.6"
35
35
  },
36
36
  "devDependencies": {
37
37
  "typescript": "^5.9.3"