@stacksjs/cloud 0.58.48 → 0.58.50

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.
Files changed (42) hide show
  1. package/dist/index.js +144 -60
  2. package/package.json +34 -33
  3. package/src/cloud/ai.ts +94 -0
  4. package/src/cloud/aws-sdk-layer/nodejs/package-lock.json +386 -0
  5. package/src/cloud/aws-sdk-layer/nodejs/package.json +15 -0
  6. package/src/cloud/cache.ts +0 -0
  7. package/src/cloud/cdn.ts +374 -0
  8. package/src/cloud/cli.ts +45 -0
  9. package/src/cloud/compute.ts +202 -0
  10. package/src/cloud/dashboard.ts +85 -0
  11. package/src/cloud/database.ts +0 -0
  12. package/src/cloud/deployment.ts +41 -0
  13. package/src/cloud/dns.ts +34 -0
  14. package/src/cloud/docs.ts +51 -0
  15. package/src/cloud/email.ts +339 -0
  16. package/src/cloud/file-system.ts +35 -0
  17. package/src/cloud/index.ts +103 -0
  18. package/src/cloud/jump-box.ts +48 -0
  19. package/src/cloud/lambda/ask/index.js +35 -0
  20. package/src/cloud/lambda/cli-setup/index.js +67 -0
  21. package/src/cloud/lambda/summarize/index.js +35 -0
  22. package/src/cloud/network.ts +32 -0
  23. package/src/cloud/package/README.md +59 -0
  24. package/src/cloud/package/package.json +63 -0
  25. package/src/cloud/permissions.ts +33 -0
  26. package/src/cloud/queue.ts +0 -0
  27. package/src/cloud/redirects.ts +45 -0
  28. package/src/cloud/router-layer/nodejs/package.json +15 -0
  29. package/src/cloud/search-engine.ts +108 -0
  30. package/src/cloud/security.ts +259 -0
  31. package/src/cloud/storage.ts +168 -0
  32. package/src/edge/origin-request.ts +49 -0
  33. package/src/helpers.ts +597 -0
  34. package/src/index.ts +3 -0
  35. package/src/runtime/README.md +116 -0
  36. package/src/runtime/bootstrap +3 -0
  37. package/src/runtime/example/lambda.ts +36 -0
  38. package/src/runtime/runtime.ts +830 -0
  39. package/src/runtime/scripts/build-layer.ts +104 -0
  40. package/src/runtime/scripts/publish-layer.ts +110 -0
  41. package/src/runtime/server.ts +39 -0
  42. package/src/types.ts +28 -0
package/dist/index.js CHANGED
@@ -406,6 +406,7 @@ var appEnv = config2.app.env === "local" ? "dev" : config2.app.env;
406
406
  var cloudName = `stacks-cloud-${appEnv}`;
407
407
  // src/cloud/index.ts
408
408
  import {Stack as Stack2} from "aws-cdk-lib";
409
+ import {config as config20} from "@stacksjs/config";
409
410
 
410
411
  // src/cloud/ai.ts
411
412
  import {Duration, CfnOutput as Output, aws_iam as iam, aws_lambda as lambda} from "aws-cdk-lib";
@@ -1347,7 +1348,7 @@ class CdnStack {
1347
1348
  }
1348
1349
  }
1349
1350
  shouldDeployApi() {
1350
- return config6.cloud.api;
1351
+ return config6.cloud.api?.deploy;
1351
1352
  }
1352
1353
  apiBehaviorOptions(scope, props) {
1353
1354
  const hostname = Fn.select(2, Fn.split("/", props.webServerUrl.url));
@@ -1472,7 +1473,6 @@ class CdnStack {
1472
1473
  if (this.shouldDeployApi()) {
1473
1474
  const keysToRemove = ["_HANDLER", "_X_AMZN_TRACE_ID", "AWS_REGION", "AWS_EXECUTION_ENV", "AWS_LAMBDA_FUNCTION_NAME", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "AWS_LAMBDA_FUNCTION_VERSION", "AWS_LAMBDA_INITIALIZATION_TYPE", "AWS_LAMBDA_LOG_GROUP_NAME", "AWS_LAMBDA_LOG_STREAM_NAME", "AWS_ACCESS_KEY", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "AWS_LAMBDA_RUNTIME_API", "LAMBDA_TASK_ROOT", "LAMBDA_RUNTIME_DIR", "_"];
1474
1475
  keysToRemove.forEach((key) => delete env2[key]);
1475
- behaviorOptions = this.apiBehaviorOptions(scope, props);
1476
1476
  }
1477
1477
  if (this.shouldDeployDocs() && !config6.app.docMode) {
1478
1478
  behaviorOptions = {
@@ -1960,34 +1960,14 @@ class NetworkStack {
1960
1960
  natGateways: 0,
1961
1961
  subnetConfiguration: [
1962
1962
  {
1963
- cidrMask: 21,
1964
- name: `${props.slug}-${props.appEnv}-public-subnet-1`,
1965
- subnetType: ec22.SubnetType.PUBLIC
1963
+ name: "public-subnet-1",
1964
+ subnetType: ec22.SubnetType.PUBLIC,
1965
+ cidrMask: 24
1966
1966
  },
1967
1967
  {
1968
- cidrMask: 21,
1969
- name: `${props.slug}-${props.appEnv}-public-subnet-2`,
1970
- subnetType: ec22.SubnetType.PUBLIC
1971
- },
1972
- {
1973
- cidrMask: 21,
1974
- name: `${props.slug}-${props.appEnv}-public-subnet-3`,
1975
- subnetType: ec22.SubnetType.PUBLIC
1976
- },
1977
- {
1978
- cidrMask: 21,
1979
- name: `${props.slug}-${props.appEnv}-private-subnet-1`,
1980
- subnetType: ec22.SubnetType.PRIVATE_ISOLATED
1981
- },
1982
- {
1983
- cidrMask: 21,
1984
- name: `${props.slug}-${props.appEnv}-private-subnet-2`,
1985
- subnetType: ec22.SubnetType.PRIVATE_ISOLATED
1986
- },
1987
- {
1988
- cidrMask: 21,
1989
- name: `${props.slug}-${props.appEnv}-private-subnet-3`,
1990
- subnetType: ec22.SubnetType.PRIVATE_ISOLATED
1968
+ name: "private-subnet-1",
1969
+ subnetType: ec22.SubnetType.PRIVATE_ISOLATED,
1970
+ cidrMask: 28
1991
1971
  }
1992
1972
  ]
1993
1973
  });
@@ -2337,11 +2317,14 @@ class PermissionsStack {
2337
2317
  }
2338
2318
 
2339
2319
  // src/cloud/compute.ts
2340
- import {Duration as Duration6, CfnOutput as Output6, aws_lambda as lambda5, aws_logs as logs, aws_secretsmanager as secretsmanager} from "aws-cdk-lib";
2320
+ import {Duration as Duration6, CfnOutput as Output6, RemovalPolicy as RemovalPolicy8, aws_ec2 as ec23, aws_ecs as ecs, aws_secretsmanager as secretsmanager} from "aws-cdk-lib";
2341
2321
  import {path as p6} from "@stacksjs/path";
2342
2322
  import {env as env6} from "@stacksjs/env";
2323
+ import {LogGroup} from "aws-cdk-lib/aws-logs";
2324
+ import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2";
2343
2325
 
2344
2326
  class ComputeStack {
2327
+ lb;
2345
2328
  apiServer;
2346
2329
  apiServerUrl;
2347
2330
  constructor(scope, props) {
@@ -2349,17 +2332,122 @@ class ComputeStack {
2349
2332
  const fileSystem = props.fileSystem;
2350
2333
  if (!fileSystem)
2351
2334
  throw new Error("The file system is missing. Please make sure it was created properly.");
2352
- this.apiServer = new lambda5.Function(scope, "WebServer", {
2353
- functionName: `${props.slug}-${props.appEnv}-web-server`,
2354
- description: "The web server for the Stacks application",
2355
- code: lambda5.Code.fromAssetImage(p6.frameworkPath("server")),
2356
- handler: lambda5.Handler.FROM_IMAGE,
2357
- runtime: lambda5.Runtime.FROM_IMAGE,
2335
+ const cluster = new ecs.Cluster(scope, "StacksCluster", {
2336
+ clusterName: `${props.slug}-${props.appEnv}-web-server-cluster`,
2337
+ vpc
2338
+ });
2339
+ const taskDefinition = new ecs.FargateTaskDefinition(scope, "TaskDefinition", {
2340
+ family: `${props.appName}-${props.appEnv}-api`,
2341
+ memoryLimitMiB: 512,
2342
+ cpu: 256,
2343
+ runtimePlatform: {
2344
+ cpuArchitecture: ecs.CpuArchitecture.ARM64
2345
+ }
2346
+ });
2347
+ const container = taskDefinition.addContainer("WebServerContainer", {
2348
+ containerName: `${props.appName}-${props.appEnv}-api`,
2349
+ image: ecs.ContainerImage.fromAsset(p6.frameworkPath("server")),
2350
+ logging: new ecs.AwsLogDriver({
2351
+ streamPrefix: `${props.appName}-${props.appEnv}-web`,
2352
+ logGroup: new LogGroup(scope, "StacksApiLogs", {
2353
+ logGroupName: "/ecs/stacks-api",
2354
+ removalPolicy: RemovalPolicy8.DESTROY
2355
+ })
2356
+ }),
2357
+ healthCheck: {
2358
+ command: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
2359
+ interval: Duration6.seconds(10),
2360
+ timeout: Duration6.seconds(5),
2361
+ retries: 3,
2362
+ startPeriod: Duration6.seconds(10)
2363
+ }
2364
+ });
2365
+ container.addPortMappings({
2366
+ containerPort: 3000,
2367
+ hostPort: 3000
2368
+ });
2369
+ const serviceSecurityGroup = new ec23.SecurityGroup(scope, "ServiceSecurityGroup", {
2370
+ securityGroupName: `${props.appName}-${props.appEnv}-api-service-sg`,
2358
2371
  vpc,
2359
- memorySize: 512,
2360
- timeout: Duration6.minutes(5),
2361
- logRetention: logs.RetentionDays.ONE_WEEK,
2362
- architecture: lambda5.Architecture.ARM_64
2372
+ description: "Stacks Security Group for API Service"
2373
+ });
2374
+ const publicLoadBalancerSG = new ec23.SecurityGroup(scope, "PublicLoadBalancerSG", {
2375
+ securityGroupName: `${props.appName}-${props.appEnv}-public-load-balancer-sg`,
2376
+ vpc,
2377
+ description: "Access to the public facing load balancer"
2378
+ });
2379
+ serviceSecurityGroup.addIngressRule(publicLoadBalancerSG, ec23.Port.allTraffic(), "Ingress from the public ALB");
2380
+ serviceSecurityGroup.addIngressRule(publicLoadBalancerSG, ec23.Port.allTraffic(), "Ingress from the public ALB");
2381
+ this.lb = new elbv2.ApplicationLoadBalancer(scope, "ApplicationLoadBalancer", {
2382
+ loadBalancerName: `${props.appName}-${props.appEnv}-alb`,
2383
+ vpc,
2384
+ vpcSubnets: {
2385
+ subnets: vpc.selectSubnets({
2386
+ subnetType: ec23.SubnetType.PUBLIC,
2387
+ onePerAz: true
2388
+ }).subnets
2389
+ },
2390
+ internetFacing: true,
2391
+ idleTimeout: Duration6.seconds(30),
2392
+ securityGroup: publicLoadBalancerSG
2393
+ });
2394
+ const serviceTargetGroup = new elbv2.ApplicationTargetGroup(scope, "ServiceTargetGroup", {
2395
+ vpc,
2396
+ targetType: elbv2.TargetType.IP,
2397
+ protocol: elbv2.ApplicationProtocol.HTTP,
2398
+ port: 3000,
2399
+ healthCheck: {
2400
+ interval: Duration6.seconds(6),
2401
+ path: "/",
2402
+ protocol: elbv2.Protocol.HTTP,
2403
+ timeout: Duration6.seconds(5),
2404
+ healthyThresholdCount: 2,
2405
+ unhealthyThresholdCount: 10
2406
+ }
2407
+ });
2408
+ const service = new ecs.FargateService(scope, "StacksApiService", {
2409
+ serviceName: `${props.appName}-${props.appEnv}-api-service`,
2410
+ cluster,
2411
+ taskDefinition,
2412
+ desiredCount: 2,
2413
+ assignPublicIp: true,
2414
+ maxHealthyPercent: 200,
2415
+ vpcSubnets: vpc.selectSubnets({
2416
+ subnetType: ec23.SubnetType.PUBLIC,
2417
+ onePerAz: true
2418
+ }),
2419
+ minHealthyPercent: 75,
2420
+ securityGroups: [serviceSecurityGroup]
2421
+ });
2422
+ service.attachToApplicationTargetGroup(serviceTargetGroup);
2423
+ publicLoadBalancerSG.addIngressRule(ec23.Peer.anyIpv4(), ec23.Port.allTraffic());
2424
+ this.lb.addListener("HttpListener", {
2425
+ port: 80,
2426
+ defaultAction: elbv2.ListenerAction.forward([serviceTargetGroup])
2427
+ });
2428
+ props.fileSystem.connections.allowFromAnyIpv4(ec23.Port.tcp(2049));
2429
+ const volumeName = `${props.slug}-${props.appEnv}-efs`;
2430
+ taskDefinition.addVolume({
2431
+ name: volumeName,
2432
+ efsVolumeConfiguration: {
2433
+ fileSystemId: props.fileSystem.fileSystemId
2434
+ }
2435
+ });
2436
+ container.addMountPoints({
2437
+ sourceVolume: volumeName,
2438
+ containerPath: "/mnt/efs",
2439
+ readOnly: false
2440
+ });
2441
+ const scaling = service.autoScaleTaskCount({ maxCapacity: 2 });
2442
+ scaling.scaleOnCpuUtilization("CpuScaling", {
2443
+ targetUtilizationPercent: 50,
2444
+ scaleInCooldown: Duration6.seconds(60),
2445
+ scaleOutCooldown: Duration6.seconds(60)
2446
+ });
2447
+ scaling.scaleOnMemoryUtilization("MemoryScaling", {
2448
+ targetUtilizationPercent: 60,
2449
+ scaleInCooldown: Duration6.seconds(60),
2450
+ scaleOutCooldown: Duration6.seconds(60)
2363
2451
  });
2364
2452
  const keysToRemove = ["_HANDLER", "_X_AMZN_TRACE_ID", "AWS_REGION", "AWS_EXECUTION_ENV", "AWS_LAMBDA_FUNCTION_NAME", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "AWS_LAMBDA_FUNCTION_VERSION", "AWS_LAMBDA_INITIALIZATION_TYPE", "AWS_LAMBDA_LOG_GROUP_NAME", "AWS_LAMBDA_LOG_STREAM_NAME", "AWS_ACCESS_KEY", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "AWS_LAMBDA_RUNTIME_API", "LAMBDA_TASK_ROOT", "LAMBDA_RUNTIME_DIR", "_"];
2365
2453
  keysToRemove.forEach((key) => delete env6[key]);
@@ -2371,23 +2459,16 @@ class ComputeStack {
2371
2459
  generateStringKey: Object.keys(env6).join(",").length.toString()
2372
2460
  }
2373
2461
  });
2374
- secrets.grantRead(this.apiServer);
2375
- this.apiServer.addEnvironment("SECRETS_ARN", secrets.secretArn);
2376
- this.apiServerUrl = new lambda5.FunctionUrl(scope, "StacksServerUrl", {
2377
- function: this.apiServer,
2378
- authType: lambda5.FunctionUrlAuthType.NONE,
2379
- cors: {
2380
- allowedOrigins: ["*"]
2381
- }
2382
- });
2462
+ secrets.grantRead(service.taskDefinition.executionRole);
2463
+ container.addEnvironment("SECRETS_ARN", secrets.secretArn);
2383
2464
  const apiPrefix = "api";
2384
2465
  new Output6(scope, "ApiUrl", {
2385
2466
  value: `https://${props.domain}/${apiPrefix}`,
2386
2467
  description: "The URL of the deployed application"
2387
2468
  });
2388
2469
  new Output6(scope, "ApiVanityUrl", {
2389
- value: this.apiServerUrl.url,
2390
- description: "The Vanity URL of the deployed application"
2470
+ value: `http://${this.lb.loadBalancerDnsName}`,
2471
+ description: "The Vanity URL / DNS name of the load balancer"
2391
2472
  });
2392
2473
  }
2393
2474
  }
@@ -2422,13 +2503,16 @@ class Cloud extends Stack2 {
2422
2503
  });
2423
2504
  new RedirectsStack(this, props);
2424
2505
  new PermissionsStack(this);
2425
- const api = new ComputeStack(this, {
2426
- ...props,
2427
- vpc: network2.vpc,
2428
- fileSystem: fileSystem.fileSystem,
2429
- zone: dns2.zone,
2430
- certificate: security2.certificate
2431
- });
2506
+ let api;
2507
+ if (config20.cloud.api?.deploy) {
2508
+ api = new ComputeStack(this, {
2509
+ ...props,
2510
+ vpc: network2.vpc,
2511
+ fileSystem: fileSystem.fileSystem,
2512
+ zone: dns2.zone,
2513
+ certificate: security2.certificate
2514
+ });
2515
+ }
2432
2516
  const ai2 = new AiStack(this, props);
2433
2517
  const cli4 = new CliStack(this, props);
2434
2518
  const cdn2 = new CdnStack(this, {
@@ -2439,11 +2523,11 @@ class Cloud extends Stack2 {
2439
2523
  firewall: security2.firewall,
2440
2524
  originRequestFunction: docs2.originRequestFunction,
2441
2525
  zone: dns2.zone,
2442
- webServer: api.apiServer,
2443
- webServerUrl: api.apiServerUrl,
2444
2526
  cliSetupUrl: cli4.cliSetupUrl,
2445
2527
  askAiUrl: ai2.askAiUrl,
2446
- summarizeAiUrl: ai2.summarizeAiUrl
2528
+ summarizeAiUrl: ai2.summarizeAiUrl,
2529
+ webServer: api?.apiServer,
2530
+ webServerUrl: api?.apiServerUrl
2447
2531
  });
2448
2532
  new DeploymentStack(this, {
2449
2533
  ...props,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/cloud",
3
3
  "type": "module",
4
- "version": "0.58.48",
4
+ "version": "0.58.50",
5
5
  "description": "The Stacks cloud/serverless integration & implementation.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -40,7 +40,8 @@
40
40
  ],
41
41
  "files": [
42
42
  "README.md",
43
- "dist"
43
+ "dist",
44
+ "src"
44
45
  ],
45
46
  "scripts": {
46
47
  "bootstrap": "bunx cdk bootstrap",
@@ -54,20 +55,20 @@
54
55
  "prepublishOnly": "bun run build"
55
56
  },
56
57
  "peerDependencies": {
57
- "@aws-sdk/client-bedrock": "^3.503.1",
58
- "@aws-sdk/client-cloudformation": "^3.503.1",
59
- "@aws-sdk/client-cloudfront": "^3.503.1",
60
- "@aws-sdk/client-cloudwatch-logs": "^3.503.1",
61
- "@aws-sdk/client-ec2": "^3.503.1",
62
- "@aws-sdk/client-efs": "^3.503.1",
63
- "@aws-sdk/client-iam": "^3.503.1",
64
- "@aws-sdk/client-lambda": "^3.503.1",
65
- "@aws-sdk/client-route-53-domains": "^3.503.1",
66
- "@aws-sdk/client-s3": "^3.503.1",
67
- "@aws-sdk/client-ses": "^3.503.1",
68
- "@aws-sdk/client-sesv2": "^3.503.1",
69
- "@aws-sdk/client-ssm": "^3.503.1",
70
- "@aws-sdk/lib-dynamodb": "^3.503.1",
58
+ "@aws-sdk/client-bedrock": "^3.504.0",
59
+ "@aws-sdk/client-cloudformation": "^3.504.0",
60
+ "@aws-sdk/client-cloudfront": "^3.504.0",
61
+ "@aws-sdk/client-cloudwatch-logs": "^3.504.0",
62
+ "@aws-sdk/client-ec2": "^3.504.0",
63
+ "@aws-sdk/client-efs": "^3.504.0",
64
+ "@aws-sdk/client-iam": "^3.504.0",
65
+ "@aws-sdk/client-lambda": "^3.504.0",
66
+ "@aws-sdk/client-route-53-domains": "^3.504.0",
67
+ "@aws-sdk/client-s3": "^3.504.0",
68
+ "@aws-sdk/client-ses": "^3.504.0",
69
+ "@aws-sdk/client-sesv2": "^3.504.0",
70
+ "@aws-sdk/client-ssm": "^3.504.0",
71
+ "@aws-sdk/lib-dynamodb": "^3.506.0",
71
72
  "@stacksjs/config": "latest",
72
73
  "@stacksjs/env": "latest",
73
74
  "@stacksjs/logging": "latest",
@@ -79,20 +80,20 @@
79
80
  "@stacksjs/validation": "latest"
80
81
  },
81
82
  "dependencies": {
82
- "@aws-sdk/client-bedrock": "^3.503.1",
83
- "@aws-sdk/client-cloudformation": "^3.503.1",
84
- "@aws-sdk/client-cloudfront": "^3.503.1",
85
- "@aws-sdk/client-cloudwatch-logs": "^3.503.1",
86
- "@aws-sdk/client-dynamodb": "3.503.1",
87
- "@aws-sdk/client-ec2": "^3.503.1",
88
- "@aws-sdk/client-efs": "^3.503.1",
89
- "@aws-sdk/client-iam": "^3.503.1",
90
- "@aws-sdk/client-lambda": "^3.503.1",
91
- "@aws-sdk/client-route-53-domains": "^3.503.1",
92
- "@aws-sdk/client-s3": "^3.503.1",
93
- "@aws-sdk/client-ses": "^3.503.1",
94
- "@aws-sdk/client-sesv2": "^3.503.1",
95
- "@aws-sdk/client-ssm": "^3.503.1",
83
+ "@aws-sdk/client-bedrock": "^3.504.0",
84
+ "@aws-sdk/client-cloudformation": "^3.504.0",
85
+ "@aws-sdk/client-cloudfront": "^3.504.0",
86
+ "@aws-sdk/client-cloudwatch-logs": "^3.504.0",
87
+ "@aws-sdk/client-dynamodb": "3.506.0",
88
+ "@aws-sdk/client-ec2": "^3.504.0",
89
+ "@aws-sdk/client-efs": "^3.504.0",
90
+ "@aws-sdk/client-iam": "^3.504.0",
91
+ "@aws-sdk/client-lambda": "^3.504.0",
92
+ "@aws-sdk/client-route-53-domains": "^3.504.0",
93
+ "@aws-sdk/client-s3": "^3.504.0",
94
+ "@aws-sdk/client-ses": "^3.504.0",
95
+ "@aws-sdk/client-sesv2": "^3.504.0",
96
+ "@aws-sdk/client-ssm": "^3.504.0",
96
97
  "@stacksjs/config": "latest",
97
98
  "@stacksjs/dns": "latest",
98
99
  "@stacksjs/env": "latest",
@@ -102,13 +103,13 @@
102
103
  "@stacksjs/storage": "latest",
103
104
  "@stacksjs/utils": "latest",
104
105
  "@stacksjs/validation": "latest",
105
- "aws-cdk": "^2.124.0",
106
- "aws-cdk-lib": "^2.124.0",
106
+ "aws-cdk": "^2.126.0",
107
+ "aws-cdk-lib": "^2.126.0",
107
108
  "aws4fetch": "^1.0.17",
108
109
  "constructs": "^10.3.0"
109
110
  },
110
111
  "devDependencies": {
111
- "@oclif/plugin-plugins": "^4.1.23",
112
+ "@oclif/plugin-plugins": "^4.2.1",
112
113
  "@stacksjs/cli": "latest",
113
114
  "@stacksjs/development": "latest",
114
115
  "@stacksjs/env": "latest",
@@ -0,0 +1,94 @@
1
+ /* eslint-disable no-new */
2
+ import { Duration, CfnOutput as Output, aws_iam as iam, aws_lambda as lambda } from 'aws-cdk-lib'
3
+ import type { Construct } from 'constructs'
4
+ import { config } from '@stacksjs/config'
5
+ import type { NestedCloudProps } from '../types'
6
+
7
+ export interface AiStackProps extends NestedCloudProps {
8
+ }
9
+
10
+ export class AiStack {
11
+ askAiUrl: lambda.FunctionUrl
12
+ summarizeAiUrl: lambda.FunctionUrl
13
+
14
+ constructor(scope: Construct, props: AiStackProps) {
15
+ // Define the Lambda Layer for aws-sdk
16
+ const awsSdkLayer = new lambda.LayerVersion(scope, 'AwsSdkLayer', {
17
+ code: lambda.Code.fromAsset('src/cloud/aws-sdk-layer'),
18
+ compatibleRuntimes: [lambda.Runtime.NODEJS_20_X],
19
+ description: 'Layer with aws-sdk',
20
+ })
21
+
22
+ const bedrockAccessPolicy = new iam.PolicyStatement({
23
+ effect: iam.Effect.ALLOW,
24
+ actions: [
25
+ 'bedrock:InvokeModel',
26
+ 'bedrock:InvokeModelWithResponseStream',
27
+ ],
28
+ resources: config.ai.models?.map(model => `arn:aws:bedrock:us-east-1::foundation-model/${model}`),
29
+ })
30
+
31
+ const bedrockAccessRole = new iam.Role(scope, 'BedrockAccessRole', {
32
+ assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
33
+ managedPolicies: [
34
+ iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
35
+ ],
36
+ })
37
+
38
+ bedrockAccessRole.addToPolicy(bedrockAccessPolicy)
39
+
40
+ const askAi = new lambda.Function(scope, 'AskAiFunction', {
41
+ functionName: `${props.slug}-${props.appEnv}-ai-ask`,
42
+ description: 'Lambda function to invoke the AI model',
43
+ runtime: lambda.Runtime.NODEJS_20_X,
44
+ handler: 'index.handler',
45
+ code: lambda.Code.fromAsset('src/cloud/lambda/ask'), // path relative to the cloud root package dir
46
+ layers: [awsSdkLayer],
47
+ role: bedrockAccessRole,
48
+ timeout: Duration.seconds(30),
49
+ })
50
+
51
+ this.askAiUrl = new lambda.FunctionUrl(scope, 'AskAiFunctionUrl', {
52
+ function: askAi,
53
+ authType: lambda.FunctionUrlAuthType.NONE,
54
+ cors: {
55
+ allowedOrigins: ['*'],
56
+ },
57
+ })
58
+
59
+ const summarizeAi = new lambda.Function(scope, 'SummarizeAiFunction', {
60
+ functionName: `${props.slug}-${props.appEnv}-ai-summarize`,
61
+ description: 'Lambda function to summarize any given text',
62
+ runtime: lambda.Runtime.NODEJS_20_X,
63
+ handler: 'index.handler',
64
+ code: lambda.Code.fromAsset('src/cloud/lambda/summarize'),
65
+ layers: [awsSdkLayer],
66
+ role: bedrockAccessRole,
67
+ timeout: Duration.seconds(30),
68
+ })
69
+
70
+ this.summarizeAiUrl = new lambda.FunctionUrl(scope, 'SummarizeAiFunctionUrl', {
71
+ function: summarizeAi,
72
+ authType: lambda.FunctionUrlAuthType.NONE,
73
+ cors: {
74
+ allowedOrigins: ['*'],
75
+ },
76
+ })
77
+
78
+ new Output(scope, 'AiVanityAskApiUrl', {
79
+ value: this.askAiUrl.url,
80
+ })
81
+
82
+ new Output(scope, 'AiVanitySummarizeApiUrl', {
83
+ value: this.summarizeAiUrl.url,
84
+ })
85
+
86
+ new Output(scope, 'AiAskApiUrl', {
87
+ value: `https://${props.domain}/ai/ask`,
88
+ })
89
+
90
+ new Output(scope, 'AiSummarizeApiUrl', {
91
+ value: `https://${props.domain}/ai/summary`,
92
+ })
93
+ }
94
+ }