@stacksjs/ts-cloud 0.5.5 → 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.
@@ -45,6 +45,12 @@ export type CodeSource = {
45
45
  kind: 'image';
46
46
  imageUri: string;
47
47
  };
48
+ /**
49
+ * AWS caps the aggregate size of a function's environment variables at 4 KB.
50
+ * Check before the API call so we fail with an actionable message (which vars,
51
+ * how far over) instead of a cryptic `InvalidParameterValueException` mid-deploy.
52
+ */
53
+ export declare function assertEnvWithinLimit(name: string, env: Record<string, string>): void;
48
54
  export declare function deployServerlessApp(config: CloudConfig, environment: EnvironmentType, opts?: DeployServerlessOptions): Promise<void>;
49
55
  export declare function redeployServerlessApp(config: CloudConfig, environment: EnvironmentType): Promise<void>;
50
56
  export declare function rollbackServerlessApp(config: CloudConfig, environment: EnvironmentType): Promise<void>;
package/dist/index.js CHANGED
@@ -25845,16 +25845,21 @@ function resolveServerlessRuntime(app) {
25845
25845
  layerEnvVar: "TSCLOUD_NODE_LAYER_ARN"
25846
25846
  };
25847
25847
  }
25848
- function resolveQueueNames(app, slug, env) {
25848
+ function resolveQueues(app, slug, env) {
25849
25849
  if (app.queues === false)
25850
25850
  return [];
25851
25851
  if (app.queues === undefined || app.queues === true)
25852
- return [`${slug}-${env}-default`];
25852
+ return [{ name: `${slug}-${env}-default` }];
25853
25853
  return app.queues.map((q) => {
25854
- const name = typeof q === "string" ? q : Object.keys(q)[0];
25855
- return `${slug}-${env}-${name}`;
25854
+ if (typeof q === "string")
25855
+ return { name: `${slug}-${env}-${q}` };
25856
+ const [name, concurrency] = Object.entries(q)[0];
25857
+ return { name: `${slug}-${env}-${name}`, concurrency };
25856
25858
  });
25857
25859
  }
25860
+ function resolveQueueNames(app, slug, env) {
25861
+ return resolveQueues(app, slug, env).map((q) => q.name);
25862
+ }
25858
25863
  function composeServerlessAppTemplate(opts) {
25859
25864
  const { app, environment, handlers } = opts;
25860
25865
  const slug = opts.config.project.slug;
@@ -25866,8 +25871,12 @@ function composeServerlessAppTemplate(opts) {
25866
25871
  queue: `${slug}-${environment}-queue`,
25867
25872
  cli: `${slug}-${environment}-cli`
25868
25873
  };
25869
- const queueNames = resolveQueueNames(app, slug, environment);
25874
+ if (app.gatewayVersion === 1)
25875
+ 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.");
25876
+ const queues = resolveQueues(app, slug, environment);
25877
+ const queueNames = queues.map((q) => q.name);
25870
25878
  const hasQueue = queueNames.length > 0;
25879
+ const logRetention = app.logRetention ?? 14;
25871
25880
  const imageMode = app.packaging === "image";
25872
25881
  const schedulerEnabled = (app.scheduler ?? "on") !== "off";
25873
25882
  const cacheEnabled = (app.cache?.driver ?? "dynamodb") === "dynamodb";
@@ -25983,7 +25992,7 @@ function composeServerlessAppTemplate(opts) {
25983
25992
  function addFunction(logicalId, name, handler8, mode, memory, timeout, reservedConcurrency, tmp = tmpStorage) {
25984
25993
  resources[`${logicalId}LogGroup`] = {
25985
25994
  Type: "AWS::Logs::LogGroup",
25986
- Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: 14 }
25995
+ Properties: { LogGroupName: `/aws/lambda/${name}`, RetentionInDays: logRetention }
25987
25996
  };
25988
25997
  const codeProps = imageMode ? {
25989
25998
  PackageType: "Image",
@@ -26124,19 +26133,21 @@ function composeServerlessAppTemplate(opts) {
26124
26133
  MessageRetentionPeriod: 1209600
26125
26134
  }
26126
26135
  };
26127
- queueNames.forEach((qName, i) => {
26136
+ queues.forEach((q, i) => {
26128
26137
  const qId = `AppQueue${i}`;
26138
+ const fnTimeout = app.queueTimeout ?? 120;
26129
26139
  resources[qId] = {
26130
26140
  Type: "AWS::SQS::Queue",
26131
26141
  Properties: {
26132
- QueueName: qName,
26133
- VisibilityTimeout: Math.max(app.queueTimeout ?? 120, app.queueTimeout ?? 120),
26142
+ QueueName: q.name,
26143
+ VisibilityTimeout: Math.min(43200, fnTimeout * 6),
26134
26144
  RedrivePolicy: {
26135
26145
  deadLetterTargetArn: Fn2.getAtt("AppQueueDlq", "Arn"),
26136
26146
  maxReceiveCount: app.queueTries ?? 3
26137
26147
  }
26138
26148
  }
26139
26149
  };
26150
+ const concurrency = q.concurrency ?? app.queueConcurrency;
26140
26151
  resources[`${qId}Mapping`] = {
26141
26152
  Type: "AWS::Lambda::EventSourceMapping",
26142
26153
  Properties: {
@@ -26144,10 +26155,10 @@ function composeServerlessAppTemplate(opts) {
26144
26155
  FunctionName: Fn2.ref("QueueFunction"),
26145
26156
  BatchSize: 1,
26146
26157
  FunctionResponseTypes: ["ReportBatchItemFailures"],
26147
- ...app.queueConcurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, app.queueConcurrency) } } : {}
26158
+ ...concurrency ? { ScalingConfig: { MaximumConcurrency: Math.max(2, concurrency) } } : {}
26148
26159
  }
26149
26160
  };
26150
- outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${qName}`, Value: Fn2.ref(qId) };
26161
+ outputs[`QueueUrl${i}`] = { Description: `Queue URL: ${q.name}`, Value: Fn2.ref(qId) };
26151
26162
  });
26152
26163
  }
26153
26164
  if (schedulerEnabled) {
@@ -83294,7 +83305,15 @@ async function uploadAssets(s32, bucket, dir, prefix, includeDotfiles = false) {
83294
83305
  }
83295
83306
  return count;
83296
83307
  }
83308
+ function assertEnvWithinLimit(name, env2) {
83309
+ const bytes = Object.entries(env2).reduce((n, [k, v]) => n + Buffer.byteLength(`${k}=${v}`, "utf8"), 0);
83310
+ if (bytes > 4096) {
83311
+ const biggest = Object.entries(env2).map(([k, v]) => [k, Buffer.byteLength(`${k}=${v}`, "utf8")]).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([k, n]) => `${k} (${n}B)`).join(", ");
83312
+ throw new Error(`${name}: environment variables total ${bytes}B, over AWS's 4096B limit (${bytes - 4096}B too large). ` + `Largest: ${biggest}. Move large/secret values out of env — store them in Secrets Manager and fetch at cold start.`);
83313
+ }
83314
+ }
83297
83315
  async function applyFunction(lambda2, name, env2, code) {
83316
+ assertEnvWithinLimit(name, env2);
83298
83317
  await withConflictRetry(() => lambda2.updateFunctionConfiguration({ FunctionName: name, Environment: { Variables: env2 } }));
83299
83318
  await lambda2.waitForFunctionActive(name, 120);
83300
83319
  const codeParams = code.kind === "image" ? { FunctionName: name, ImageUri: code.imageUri } : { FunctionName: name, S3Bucket: code.bucket, S3Key: code.key };
@@ -83449,6 +83468,7 @@ async function deployServerlessApp(config6, environment, opts = {}) {
83449
83468
  code: codeSource,
83450
83469
  previousSha: prior?.sha,
83451
83470
  previousCode: prior?.code,
83471
+ previousFunctionEnv: prior?.functionEnv,
83452
83472
  functionEnv,
83453
83473
  functionNames: {
83454
83474
  http: composed.functionNames.http,
@@ -83520,14 +83540,17 @@ async function rollbackServerlessApp(config6, environment) {
83520
83540
  if (!name)
83521
83541
  continue;
83522
83542
  step(`Restoring ${mode} (${name})`);
83523
- await applyFunction(lambda2, name, release.functionEnv[mode] ?? {}, release.previousCode);
83543
+ const env2 = release.previousFunctionEnv?.[mode] ?? release.functionEnv[mode] ?? {};
83544
+ await applyFunction(lambda2, name, env2, release.previousCode);
83524
83545
  }
83525
83546
  await writeRelease(s32, ctx.artifactBucket, ctx.slug, environment, {
83526
83547
  ...release,
83527
83548
  sha: release.previousSha ?? release.sha,
83528
83549
  code: release.previousCode,
83550
+ functionEnv: release.previousFunctionEnv ?? release.functionEnv,
83529
83551
  previousSha: undefined,
83530
83552
  previousCode: undefined,
83553
+ previousFunctionEnv: undefined,
83531
83554
  timestamp: new Date().toISOString()
83532
83555
  });
83533
83556
  success("Rollback complete");
@@ -86732,6 +86755,7 @@ export {
86732
86755
  resolveServerlessArtifactBucketName,
86733
86756
  resolveServerlessAppStackName,
86734
86757
  resolveRegion,
86758
+ resolveQueues,
86735
86759
  resolveQueueNames,
86736
86760
  resolveProjectStackName,
86737
86761
  resolveObjectStorage,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/ts-cloud",
3
3
  "type": "module",
4
- "version": "0.5.5",
4
+ "version": "0.5.6",
5
5
  "description": "A lightweight, performant infrastructure-as-code library and CLI for deploying both server-based (EC2) and serverless applications.",
6
6
  "author": "Chris Breuer <chris@stacksjs.com>",
7
7
  "license": "MIT",
@@ -89,8 +89,8 @@
89
89
  "test": "bun test"
90
90
  },
91
91
  "dependencies": {
92
- "@ts-cloud/aws-types": "0.5.5",
93
- "@ts-cloud/core": "0.5.5",
92
+ "@ts-cloud/aws-types": "0.5.6",
93
+ "@ts-cloud/core": "0.5.6",
94
94
  "@stacksjs/ts-xml": "^0.1.0"
95
95
  },
96
96
  "devDependencies": {