@fjall/components-infrastructure 2.30.3 → 2.32.0

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 (118) hide show
  1. package/dist/lib/app.d.ts +27 -1
  2. package/dist/lib/app.js +35 -4
  3. package/dist/lib/config/aws/identityCenterMembership.d.ts +11 -0
  4. package/dist/lib/config/aws/identityCenterMembership.js +61 -0
  5. package/dist/lib/config/aws/oidcConnector.js +11 -3
  6. package/dist/lib/lambda-assets/cert-generator/asset/index.js +12 -3
  7. package/dist/lib/patterns/aws/compute.d.ts +2 -2
  8. package/dist/lib/patterns/aws/compute.js +18 -9
  9. package/dist/lib/patterns/aws/computeLambda.d.ts +15 -1
  10. package/dist/lib/patterns/aws/computeLambda.js +18 -2
  11. package/dist/lib/patterns/aws/computePropApplicability.d.ts +20 -0
  12. package/dist/lib/patterns/aws/computePropApplicability.js +87 -0
  13. package/dist/lib/patterns/aws/database.d.ts +19 -1
  14. package/dist/lib/patterns/aws/database.js +22 -3
  15. package/dist/lib/patterns/aws/devSubstrate.d.ts +85 -0
  16. package/dist/lib/patterns/aws/devSubstrate.js +390 -0
  17. package/dist/lib/patterns/aws/devSubstrate.waker.source.cjs +13 -0
  18. package/dist/lib/resources/aws/compute/applicationLoadBalancer.d.ts +17 -0
  19. package/dist/lib/resources/aws/compute/applicationLoadBalancer.js +23 -0
  20. package/dist/lib/resources/aws/compute/ecsImages.js +7 -2
  21. package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +4 -10
  22. package/dist/lib/resources/aws/compute/ecsNetworking.js +13 -48
  23. package/dist/lib/resources/aws/compute/ecsRoles.d.ts +25 -0
  24. package/dist/lib/resources/aws/compute/ecsRoles.js +39 -20
  25. package/dist/lib/resources/aws/compute/hostHeaderListenerRule.d.ts +23 -0
  26. package/dist/lib/resources/aws/compute/hostHeaderListenerRule.js +59 -0
  27. package/dist/lib/resources/aws/compute/imageTagParameter.d.ts +18 -1
  28. package/dist/lib/resources/aws/compute/imageTagParameter.js +32 -2
  29. package/dist/lib/resources/aws/compute/lambda.d.ts +1 -1
  30. package/dist/lib/resources/aws/compute/lambda.js +6 -4
  31. package/dist/lib/resources/aws/compute/lambdaAlbTarget.d.ts +17 -0
  32. package/dist/lib/resources/aws/compute/lambdaAlbTarget.js +18 -0
  33. package/dist/lib/resources/aws/compute/listenerRouting.d.ts +21 -0
  34. package/dist/lib/resources/aws/compute/listenerRouting.js +20 -0
  35. package/dist/lib/resources/aws/database/rdsAurora.d.ts +16 -0
  36. package/dist/lib/resources/aws/database/rdsAurora.js +57 -3
  37. package/dist/lib/resources/aws/database/rdsDefaults.d.ts +28 -0
  38. package/dist/lib/resources/aws/database/rdsDefaults.js +28 -0
  39. package/dist/lib/resources/aws/iam/identityCenter/user.js +5 -1
  40. package/dist/lib/resources/aws/logging/index.d.ts +1 -0
  41. package/dist/lib/resources/aws/logging/index.js +1 -0
  42. package/dist/lib/resources/aws/logging/logGroup.d.ts +7 -0
  43. package/dist/lib/resources/aws/logging/logGroup.js +7 -0
  44. package/dist/lib/resources/aws/logging/singletonLogGroup.d.ts +15 -0
  45. package/dist/lib/resources/aws/logging/singletonLogGroup.js +24 -0
  46. package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +2 -1
  47. package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +5 -6
  48. package/dist/lib/resources/aws/storage/bucketDeployment.js +14 -7
  49. package/dist/lib/resources/aws/utilities/awsCustomResource.js +5 -0
  50. package/dist/lib/resources/aws/utilities/customResource.js +5 -1
  51. package/dist/lib/utils/addSuffixToEmail.js +1 -5
  52. package/dist/lib/utils/devSubstrateTags.d.ts +22 -0
  53. package/dist/lib/utils/devSubstrateTags.js +22 -0
  54. package/dist/lib/utils/validationLogger.d.ts +2 -2
  55. package/dist/lib/utils/validationLogger.js +4 -4
  56. package/package.json +5 -5
  57. package/dist/lib/__tests__/setup.d.ts +0 -48
  58. package/dist/lib/__tests__/setup.js +0 -1
  59. package/dist/lib/config/aws/accountId.d.ts +0 -6
  60. package/dist/lib/config/aws/accountId.js +0 -33
  61. package/dist/lib/config/aws/backupGlobalSettings.d.ts +0 -29
  62. package/dist/lib/config/aws/backupGlobalSettings.js +0 -51
  63. package/dist/lib/config/aws/costAllocationTags.d.ts +0 -12
  64. package/dist/lib/config/aws/costAllocationTags.js +0 -48
  65. package/dist/lib/config/aws/eventBus.d.ts +0 -7
  66. package/dist/lib/config/aws/eventBus.js +0 -24
  67. package/dist/lib/config/aws/identityCenterGroupMembership.d.ts +0 -10
  68. package/dist/lib/config/aws/identityCenterGroupMembership.js +0 -115
  69. package/dist/lib/config/aws/ipamDelegateAdmin.d.ts +0 -8
  70. package/dist/lib/config/aws/ipamDelegateAdmin.js +0 -58
  71. package/dist/lib/config/aws/ipamPoolId.d.ts +0 -10
  72. package/dist/lib/config/aws/ipamPoolId.js +0 -39
  73. package/dist/lib/config/aws/organisation.d.ts +0 -30
  74. package/dist/lib/config/aws/organisation.js +0 -95
  75. package/dist/lib/config/aws/organisationId.d.ts +0 -7
  76. package/dist/lib/config/aws/organisationId.js +0 -46
  77. package/dist/lib/config/aws/organisationsAccess.d.ts +0 -10
  78. package/dist/lib/config/aws/organisationsAccess.js +0 -50
  79. package/dist/lib/config/aws/ramSharing.d.ts +0 -4
  80. package/dist/lib/config/aws/ramSharing.js +0 -35
  81. package/dist/lib/config/monitoring.d.ts +0 -18
  82. package/dist/lib/config/monitoring.js +0 -22
  83. package/dist/lib/patterns/aws/cicdRole.d.ts +0 -67
  84. package/dist/lib/patterns/aws/cicdRole.js +0 -68
  85. package/dist/lib/patterns/aws/hostedZone.d.ts +0 -28
  86. package/dist/lib/patterns/aws/hostedZone.js +0 -150
  87. package/dist/lib/patterns/aws/managedAccount.d.ts +0 -9
  88. package/dist/lib/patterns/aws/managedAccount.js +0 -55
  89. package/dist/lib/patterns/aws/managedIdentityCenter.d.ts +0 -4
  90. package/dist/lib/patterns/aws/managedIdentityCenter.js +0 -24
  91. package/dist/lib/patterns/aws/managedOrganisation.d.ts +0 -36
  92. package/dist/lib/patterns/aws/managedOrganisation.js +0 -97
  93. package/dist/lib/patterns/aws/managedPlatform.d.ts +0 -12
  94. package/dist/lib/patterns/aws/managedPlatform.js +0 -29
  95. package/dist/lib/patterns/aws/subdomainHostedZone.d.ts +0 -9
  96. package/dist/lib/patterns/aws/subdomainHostedZone.js +0 -39
  97. package/dist/lib/resources/aws/cicd/cicdRole.d.ts +0 -65
  98. package/dist/lib/resources/aws/cicd/cicdRole.js +0 -191
  99. package/dist/lib/resources/aws/compute/ecsFreeTier.d.ts +0 -75
  100. package/dist/lib/resources/aws/compute/ecsFreeTier.js +0 -1
  101. package/dist/lib/resources/aws/compute/ecsSpot.d.ts +0 -75
  102. package/dist/lib/resources/aws/compute/ecsSpot.js +0 -1
  103. package/dist/lib/resources/aws/compute/utilities/capacityProviderDrainWaiter.d.ts +0 -20
  104. package/dist/lib/resources/aws/compute/utilities/capacityProviderDrainWaiter.js +0 -1
  105. package/dist/lib/resources/aws/database/database.d.ts +0 -14
  106. package/dist/lib/resources/aws/database/database.js +0 -28
  107. package/dist/lib/resources/aws/database/databaseInstance.d.ts +0 -15
  108. package/dist/lib/resources/aws/database/databaseInstance.js +0 -30
  109. package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.d.ts +0 -13
  110. package/dist/lib/resources/aws/iam/identityCenter/attachManagedPolicy.js +0 -52
  111. package/dist/lib/resources/aws/iam/securityGroup.d.ts +0 -5
  112. package/dist/lib/resources/aws/iam/securityGroup.js +0 -14
  113. package/dist/lib/resources/aws/monitoring/monitoringRole.d.ts +0 -28
  114. package/dist/lib/resources/aws/monitoring/monitoringRole.js +0 -125
  115. package/dist/lib/resources/aws/utilities/cfnOutput.d.ts +0 -5
  116. package/dist/lib/resources/aws/utilities/cfnOutput.js +0 -1
  117. package/dist/lib/utils/capitalizeString.d.ts +0 -1
  118. package/dist/lib/utils/capitalizeString.js +0 -7
package/dist/lib/app.d.ts CHANGED
@@ -16,6 +16,7 @@ import { type RemovalPolicyString } from "./resources/aws/messaging/utils.js";
16
16
  import { type ServiceRegistrationProps } from "./resources/aws/networking/serviceDiscovery.js";
17
17
  import { type IPrivateDnsNamespace, type IService } from "aws-cdk-lib/aws-servicediscovery";
18
18
  import { type AnyCompute } from "./patterns/aws/compute.js";
19
+ import { DevSubstrate, type IDevSubstrateProps } from "./patterns/aws/devSubstrate.js";
19
20
  import { type Storage, type StorageFactoryFn } from "./patterns/aws/storage.js";
20
21
  import { type AnyPattern, type PatternFactoryFn } from "./patterns/aws/pattern.js";
21
22
  import { type BackupTier } from "./utils/backupTierMapping.js";
@@ -242,6 +243,23 @@ export declare class App extends CdkApp {
242
243
  */
243
244
  addCompute<T extends AnyCompute & Construct>(fn: (app: App, scope: Construct) => T): T;
244
245
  addComputeResource(resource: Construct): void;
246
+ /**
247
+ * Instantiate the internal `DevSubstrate` construct on the default compute
248
+ * stack (fast dev-envs Phase 3).
249
+ *
250
+ * Deliberately NOT routed through `PATTERN_TYPE_VALUES` / the pattern registry
251
+ * / `PatternFactory` / the `IPatternProps` union — the substrate is
252
+ * Fjall-managed dev infrastructure, never offered on the customer
253
+ * create/scaffold surface (design R5).
254
+ *
255
+ * Placement is load-bearing at DEPLOY time, not just synth: deploy-core selects
256
+ * stacks by a fixed six-category enum and derives deploy-order flags from
257
+ * post-synth stack-name suffixes (`endsWith("Compute")`), so scoping the
258
+ * substrate under `${stackPrefix}Compute` via `getDefaultComputeStack()` is
259
+ * what makes it actually deploy (design VF2). A dedicated `…DevSubstrate` stack
260
+ * would synth and hash but never deploy.
261
+ */
262
+ addDevSubstrate(props: IDevSubstrateProps): DevSubstrate;
245
263
  /**
246
264
  * Add a database resource to the default database stack using the factory pattern.
247
265
  * Returns the appropriate database type based on the factory used.
@@ -341,8 +359,16 @@ export declare class App extends CdkApp {
341
359
  * visibilityTimeout: 300,
342
360
  * contentBasedDeduplication: true
343
361
  * }));
362
+ *
363
+ * Pass `stackPlacement: "compute"` to co-locate the queue in the compute stack
364
+ * (mirrors `addStorage`/`addSchedule`). Required when a bucket's notification
365
+ * targets the queue within one app: an S3 notification and its auto-generated
366
+ * SQS queue policy reference each other's ARN, so splitting the two across the
367
+ * Storage and Messaging stacks synthesises a fatal cross-stack dependency cycle.
344
368
  */
345
- addMessaging<T extends AnyMessaging & Construct>(fn: (app: App, scope: Construct) => T): T;
369
+ addMessaging<T extends AnyMessaging & Construct>(fn: (app: App, scope: Construct) => T, opts?: {
370
+ stackPlacement?: "messaging" | "compute";
371
+ }): T;
346
372
  /**
347
373
  * Register an EventBridge schedule against a Fjall wrapper target.
348
374
  *
package/dist/lib/app.js CHANGED
@@ -9,6 +9,7 @@ import { NetworkFactory } from "./patterns/aws/network.js";
9
9
  import { MessagingFactory } from "./patterns/aws/messaging.js";
10
10
  import { Schedule } from "./resources/aws/messaging/schedule.js";
11
11
  import { ServiceDiscoveryNamespace } from "./resources/aws/networking/serviceDiscovery.js";
12
+ import { DevSubstrate } from "./patterns/aws/devSubstrate.js";
12
13
  import { StandardTagsAspect } from "./utils/standardTagsAspect.js";
13
14
  import { BACKUP_TIER_TAG_KEY, BACKUP_TIER_TAG_MAP } from "./utils/backupTierMapping.js";
14
15
  import { randomBytes } from "crypto";
@@ -413,6 +414,28 @@ export class App extends CdkApp {
413
414
  const computeStack = this.getDefaultComputeStack();
414
415
  computeStack.addConstruct(resource);
415
416
  }
417
+ /**
418
+ * Instantiate the internal `DevSubstrate` construct on the default compute
419
+ * stack (fast dev-envs Phase 3).
420
+ *
421
+ * Deliberately NOT routed through `PATTERN_TYPE_VALUES` / the pattern registry
422
+ * / `PatternFactory` / the `IPatternProps` union — the substrate is
423
+ * Fjall-managed dev infrastructure, never offered on the customer
424
+ * create/scaffold surface (design R5).
425
+ *
426
+ * Placement is load-bearing at DEPLOY time, not just synth: deploy-core selects
427
+ * stacks by a fixed six-category enum and derives deploy-order flags from
428
+ * post-synth stack-name suffixes (`endsWith("Compute")`), so scoping the
429
+ * substrate under `${stackPrefix}Compute` via `getDefaultComputeStack()` is
430
+ * what makes it actually deploy (design VF2). A dedicated `…DevSubstrate` stack
431
+ * would synth and hash but never deploy.
432
+ */
433
+ addDevSubstrate(props) {
434
+ const computeStack = this.getDefaultComputeStack();
435
+ const substrate = new DevSubstrate(computeStack.getStack(), "DevSubstrate", this, props);
436
+ computeStack.addConstruct(substrate);
437
+ return substrate;
438
+ }
416
439
  /**
417
440
  * Add a database resource to the default database stack using the factory pattern.
418
441
  * Returns the appropriate database type based on the factory used.
@@ -556,11 +579,19 @@ export class App extends CdkApp {
556
579
  * visibilityTimeout: 300,
557
580
  * contentBasedDeduplication: true
558
581
  * }));
582
+ *
583
+ * Pass `stackPlacement: "compute"` to co-locate the queue in the compute stack
584
+ * (mirrors `addStorage`/`addSchedule`). Required when a bucket's notification
585
+ * targets the queue within one app: an S3 notification and its auto-generated
586
+ * SQS queue policy reference each other's ARN, so splitting the two across the
587
+ * Storage and Messaging stacks synthesises a fatal cross-stack dependency cycle.
559
588
  */
560
- addMessaging(fn) {
561
- const messagingStack = this.getDefaultMessagingStack();
562
- const messaging = fn(this, messagingStack.getStack());
563
- messagingStack.addConstruct(messaging);
589
+ addMessaging(fn, opts = {}) {
590
+ const stack = opts.stackPlacement === "compute"
591
+ ? this.getDefaultComputeStack()
592
+ : this.getDefaultMessagingStack();
593
+ const messaging = fn(this, stack.getStack());
594
+ stack.addConstruct(messaging);
564
595
  return messaging;
565
596
  }
566
597
  /**
@@ -0,0 +1,11 @@
1
+ import { NestedStack, type NestedStackProps } from "aws-cdk-lib";
2
+ import { type Construct } from "constructs";
3
+ export interface IdentityCenterMembershipProps extends NestedStackProps {
4
+ identityStoreId: string;
5
+ groupId: string;
6
+ groupMembers: string[];
7
+ }
8
+ export declare function validateIdentityCenterMembershipProps(props: IdentityCenterMembershipProps): void;
9
+ export declare class IdentityCenterMembership extends NestedStack {
10
+ constructor(scope: Construct, id: string, props: IdentityCenterMembershipProps);
11
+ }
@@ -0,0 +1,61 @@
1
+ import * as customResources from "aws-cdk-lib/custom-resources";
2
+ import { NestedStack } from "aws-cdk-lib";
3
+ import { AwsCustomResource } from "../../resources/aws/utilities/awsCustomResource.js";
4
+ import { GroupMembership } from "../../resources/aws/iam/identityCenter/groupMembership.js";
5
+ import { stripAndCamelCase } from "../../utils/stripAndCamelCase.js";
6
+ const IDENTITY_STORE_SERVICE = "identityStore";
7
+ export function validateIdentityCenterMembershipProps(props) {
8
+ if (!props.identityStoreId) {
9
+ throw new Error("IdentityCenterMembership requires identityStoreId from the parent IdentityCenter construct.");
10
+ }
11
+ if (!props.groupId) {
12
+ throw new Error("IdentityCenterMembership requires groupId from a Group construct.");
13
+ }
14
+ if (props.groupMembers.length === 0) {
15
+ throw new Error("IdentityCenterMembership requires at least one member email. Empty groups should be skipped at the caller.");
16
+ }
17
+ const seen = new Set();
18
+ for (const member of props.groupMembers) {
19
+ if (!member.includes("@")) {
20
+ throw new Error(`IdentityCenterMembership: "${member}" is not a valid email — Identity Center memberships look up users by UserName (email)`);
21
+ }
22
+ if (seen.has(member)) {
23
+ throw new Error(`Duplicate member "${member}" in IdentityCenterMembership groupMembers.`);
24
+ }
25
+ seen.add(member);
26
+ }
27
+ }
28
+ export class IdentityCenterMembership extends NestedStack {
29
+ constructor(scope, id, props) {
30
+ super(scope, id, props);
31
+ validateIdentityCenterMembershipProps(props);
32
+ for (const member of props.groupMembers) {
33
+ const [localPart] = member.split("@");
34
+ const suffix = stripAndCamelCase(localPart ?? member);
35
+ const listUsersCall = {
36
+ service: IDENTITY_STORE_SERVICE,
37
+ action: "listUsers",
38
+ parameters: {
39
+ IdentityStoreId: props.identityStoreId,
40
+ Filters: [
41
+ {
42
+ AttributePath: "UserName",
43
+ AttributeValue: member
44
+ }
45
+ ]
46
+ },
47
+ physicalResourceId: customResources.PhysicalResourceId.of(`listUsers${suffix}`)
48
+ };
49
+ const userLookup = new AwsCustomResource(this, `User${suffix}`, {
50
+ onCreate: listUsersCall,
51
+ onUpdate: listUsersCall
52
+ });
53
+ const userId = userLookup.getResponseField("Users.0.UserId");
54
+ new GroupMembership(this, `Membership${suffix}`, {
55
+ identityStoreId: props.identityStoreId,
56
+ groupId: props.groupId,
57
+ userId
58
+ });
59
+ }
60
+ }
61
+ }
@@ -1,5 +1,5 @@
1
1
  import { DEV_BOUNDARY_POLICY_NAME, DEV_DEPLOY_POLICY_NAME, DEV_PROVISIONER_POLICY_NAME, DEV_SYNC_WRITER_POLICY_NAME, FJALL_OIDC_AUDIENCE, FJALL_OIDC_ISSUER_DOMAIN, FJALL_OIDC_PLACEHOLDER_THUMBPRINT, SECURITY_TIER_TO_BOUNDARY, buildDevBoundaryStatements, buildDevDeployPolicyStatements, buildDevDeployTrustStatements, buildDevProvisionerPolicyStatements, buildDevProvisionerTrustStatements, buildDevSyncWriterPolicyStatements, buildDevSyncWriterTrustStatements, buildNarrowedDeployTrustStatements, deployBoundaryName, devDeployRoleName, devProvisionerRoleName, devSyncWriterRoleName } from "@fjall/generator";
2
- import { CfnOutput, Duration, Stack } from "aws-cdk-lib";
2
+ import { Aws, CfnOutput, Duration, Stack } from "aws-cdk-lib";
3
3
  import * as iam from "aws-cdk-lib/aws-iam";
4
4
  import { Runtime } from "aws-cdk-lib/aws-lambda";
5
5
  import { Construct } from "constructs";
@@ -80,15 +80,23 @@ export class OidcConnector extends Construct {
80
80
  resourceType: "Custom::FjallOidcProvider",
81
81
  properties: { IssuerUrl: issuerUrl },
82
82
  inlinePolicy: [
83
+ // iam:ListOpenIDConnectProviders supports no resource-level
84
+ // permission — the star grants provider-list metadata only.
85
+ new iam.PolicyStatement({
86
+ effect: iam.Effect.ALLOW,
87
+ actions: ["iam:ListOpenIDConnectProviders"],
88
+ resources: ["*"]
89
+ }),
83
90
  new iam.PolicyStatement({
84
91
  effect: iam.Effect.ALLOW,
85
92
  actions: [
86
- "iam:ListOpenIDConnectProviders",
87
93
  "iam:GetOpenIDConnectProvider",
88
94
  "iam:CreateOpenIDConnectProvider",
89
95
  "iam:AddClientIDToOpenIDConnectProvider"
90
96
  ],
91
- resources: ["*"]
97
+ resources: [
98
+ `arn:${Aws.PARTITION}:iam::${Aws.ACCOUNT_ID}:oidc-provider/${issuerDomain}`
99
+ ]
92
100
  })
93
101
  ],
94
102
  inlineCode: OIDC_PROVIDER_HANDLER
@@ -3,11 +3,20 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __esm = (fn, res) => function __init() {
7
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ var __esm = (fn, res, err) => function __init() {
7
+ if (err) throw err[0];
8
+ try {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ } catch (e) {
11
+ throw err = [e], e;
12
+ }
8
13
  };
9
14
  var __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
15
+ try {
16
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ } catch (e) {
18
+ throw mod = 0, e;
19
+ }
11
20
  };
12
21
  var __export = (target, all) => {
13
22
  for (var name in all)
@@ -4,9 +4,9 @@ import { type SecretImport } from "../../resources/aws/secrets/index.js";
4
4
  import { type ComputeType, type IEcsCompute, type ILambdaCompute, type IEc2Compute, type AnyCompute, isCompute, isEcsCompute, isLambdaCompute, isEc2Compute } from "./interfaces/compute.js";
5
5
  import type App from "../../app.js";
6
6
  import { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type ServiceLogAlarm, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, type ContainerDependency, type EcsMigrationsConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, type ResolvedScalingConfig, resolveScalingConfig } from "./computeEcs.js";
7
- import { LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions } from "./computeLambda.js";
7
+ import { LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, lambdaImageKey, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions } from "./computeLambda.js";
8
8
  import { Ec2Compute, type Ec2ComputeProps, type SshConfig } from "./computeEc2.js";
9
- export { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type ServiceLogAlarm, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, type ContainerDependency, type EcsMigrationsConfig, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, type ResolvedScalingConfig, resolveScalingConfig, LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions, Ec2Compute, type Ec2ComputeProps, type SshConfig };
9
+ export { EcsCompute, type EcsComputeProps, type EcsServiceConfig, type ServiceLogAlarm, type EcsContainerConfig, type EcsScalingConfig, type EcsClusterConfig, type EcsRoutingConfig, type EcsCapacityProviderConfig, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, type EcsCapacityProvider, type Ec2CapacityConfig, type ContainerDependency, type EcsMigrationsConfig, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, type ResolvedScalingConfig, resolveScalingConfig, LambdaCompute, type LambdaComputeProps, type ContainerLambdaProps, type CodeLambdaProps, type FunctionUrlConfig, type ResolvedLambdaDeployment, resolveLambdaDeployment, lambdaImageKey, Architecture, HttpMethod, InvokeMode, type FunctionUrlCorsOptions, Ec2Compute, type Ec2ComputeProps, type SshConfig };
10
10
  export type { ComputeType } from "./interfaces/compute.js";
11
11
  /**
12
12
  * Configuration defaults for each compute type.
@@ -3,18 +3,19 @@ import { Runtime } from "aws-cdk-lib/aws-lambda";
3
3
  import { buildParameterPath } from "@fjall/util";
4
4
  import { isCompute, isEcsCompute, isLambdaCompute, isEc2Compute } from "./interfaces/compute.js";
5
5
  import { warnIfPropertiesIgnored } from "../../utils/validationLogger.js";
6
+ import { COMPUTE_WARN_GROUPS, CONTAINER_LAMBDA_ONLY_PROPS, CODE_LAMBDA_ONLY_PROPS } from "./computePropApplicability.js";
6
7
  import { applyInferredPublicBuildArgs } from "./computeBuildArgInference.js";
7
8
  import { DEFAULT_ECS_FALLBACK_IMAGE, DEFAULT_EC2_INSTANCE_TYPE } from "../../resources/aws/compute/ecsConstants.js";
8
9
  // Import and re-export from per-pattern files
9
10
  import { EcsCompute, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, resolveScalingConfig } from "./computeEcs.js";
10
- import { LambdaCompute, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode } from "./computeLambda.js";
11
+ import { LambdaCompute, resolveLambdaDeployment, lambdaImageKey, Architecture, HttpMethod, InvokeMode } from "./computeLambda.js";
11
12
  import { Ec2Compute } from "./computeEc2.js";
12
13
  // Re-export everything from per-pattern files
13
14
  export {
14
15
  // ECS
15
16
  EcsCompute, ECS_CAPACITY_PROVIDER_CONFIG, getEcsCapacityProviderConfig, ScalingType, validateEcsProps, buildContainerConfigs, expandMigrationsSugar, resolveScalingConfig,
16
17
  // Lambda
17
- LambdaCompute, resolveLambdaDeployment, Architecture, HttpMethod, InvokeMode,
18
+ LambdaCompute, resolveLambdaDeployment, lambdaImageKey, Architecture, HttpMethod, InvokeMode,
18
19
  // EC2
19
20
  Ec2Compute };
20
21
  export const COMPUTE_TYPE_CONFIG = {
@@ -90,12 +91,20 @@ export function getComputeTypeConfig(type) {
90
91
  * TypeScript's compile-time checks may not apply.
91
92
  */
92
93
  function validateComputeProps(props) {
93
- // ECS-specific options on non-ECS compute
94
- warnIfPropertiesIgnored(props, ["capacityProvider", "ec2Config", "services", "cluster"], "ecs", "ECS compute");
95
- // Lambda-specific options on non-Lambda compute
96
- warnIfPropertiesIgnored(props, ["functionUrl", "handler", "runtime"], "lambda", "Lambda compute");
97
- // EC2-specific options on non-EC2 compute
98
- warnIfPropertiesIgnored(props, ["ssh", "userData", "machineImage"], "ec2", "EC2 compute");
94
+ for (const group of COMPUTE_WARN_GROUPS) {
95
+ warnIfPropertiesIgnored(props, group.properties, group.applicableTypes, group.typeName);
96
+ }
97
+ // capacityProvider/ec2Config are EcsServiceConfig-level keys, not top-level
98
+ // EcsComputeProps, so the guard Record in computePropApplicability.ts would
99
+ // reject them hand-curated misuse hints stay outside it.
100
+ warnIfPropertiesIgnored(props, ["capacityProvider", "ec2Config"], "ecs", "ECS compute");
101
+ if (props.type === "lambda") {
102
+ // warnIfPropertyIgnored reads only `.type` + presence, so a
103
+ // deployment-keyed view reuses the same machinery for the second axis.
104
+ const byDeployment = { ...props, type: props.deployment };
105
+ warnIfPropertiesIgnored(byDeployment, CONTAINER_LAMBDA_ONLY_PROPS, "container", "container-deployment Lambda", "deployment");
106
+ warnIfPropertiesIgnored(byDeployment, CODE_LAMBDA_ONLY_PROPS, "code", "code-deployment Lambda", "deployment");
107
+ }
99
108
  // ECS-specific validation - type narrowing handled by discriminated union
100
109
  if (props.type === "ecs") {
101
110
  validateEcsProps(props);
@@ -249,7 +258,7 @@ export class ComputeFactory {
249
258
  if (lambdaComputeProps.deployment === "container" &&
250
259
  lambdaComputeProps.docker !== undefined) {
251
260
  manifestLambda.docker = lambdaComputeProps.docker;
252
- manifestLambda.imageKey = lambdaComputeProps.image ?? id;
261
+ manifestLambda.imageKey = lambdaImageKey(lambdaComputeProps, id);
253
262
  }
254
263
  collector.addLambda(manifestLambda);
255
264
  return new LambdaCompute(scope, id, lambdaComputeProps);
@@ -185,7 +185,9 @@ export interface CodeLambdaProps extends BaseLambdaProps {
185
185
  * Lambda compute configuration.
186
186
  *
187
187
  * Discriminated union ensuring type-safe Lambda configuration:
188
- * - `deployment: "container"` requires `ecrRepository`
188
+ * - `deployment: "container"` takes `ecrRepository` (bring-your-own image)
189
+ * and/or `docker` (fjall builds and pushes; `ecrRepository` then defaults
190
+ * to the app's shared default container registry)
189
191
  * - `deployment: "code"` requires `code` and allows `handler`/`runtime`
190
192
  *
191
193
  * @example
@@ -205,6 +207,18 @@ export interface ResolvedLambdaDeployment {
205
207
  handler: string;
206
208
  runtime: Runtime;
207
209
  }
210
+ /**
211
+ * Shared derivation of a container Lambda's image key. Consumed by both the
212
+ * synth-side CFN image-tag parameter (below) and the manifest `imageKey`
213
+ * (compute.ts) — `fjall deploy` matches manifest imageKey → built image tag →
214
+ * CFN parameter name, so the two derivations must stay byte-identical.
215
+ *
216
+ * An explicit empty-string `image` is treated as absent (falls back to `id`):
217
+ * `??` alone would let `image: ""` through as an empty build/tag key.
218
+ */
219
+ export declare function lambdaImageKey(props: {
220
+ image?: string;
221
+ }, id: string): string;
208
222
  /**
209
223
  * Resolve Lambda deployment configuration from props.
210
224
  * Handles container vs code deployment types.
@@ -10,6 +10,18 @@ import { evaluateBakeGuard } from "@fjall/util/docker";
10
10
  import { toKebab } from "@fjall/util";
11
11
  // Re-export Lambda types from CDK for user convenience
12
12
  export { Architecture, HttpMethod, InvokeMode } from "aws-cdk-lib/aws-lambda";
13
+ /**
14
+ * Shared derivation of a container Lambda's image key. Consumed by both the
15
+ * synth-side CFN image-tag parameter (below) and the manifest `imageKey`
16
+ * (compute.ts) — `fjall deploy` matches manifest imageKey → built image tag →
17
+ * CFN parameter name, so the two derivations must stay byte-identical.
18
+ *
19
+ * An explicit empty-string `image` is treated as absent (falls back to `id`):
20
+ * `??` alone would let `image: ""` through as an empty build/tag key.
21
+ */
22
+ export function lambdaImageKey(props, id) {
23
+ return props.image !== undefined && props.image !== "" ? props.image : id;
24
+ }
13
25
  /**
14
26
  * Resolve Lambda deployment configuration from props.
15
27
  * Handles container vs code deployment types.
@@ -33,8 +45,12 @@ export function resolveLambdaDeployment(scope, id, props) {
33
45
  if (findings.length > 0) {
34
46
  throw new Error(`Lambda '${id}': ${findings.map((f) => f.message).join(" ")}`);
35
47
  }
36
- const tagKey = props.image ?? id;
37
- const param = getOrCreateImageTagParameter(scope, tagKey);
48
+ const tagKey = lambdaImageKey(props, id);
49
+ const param = getOrCreateImageTagParameter(scope, tagKey, {
50
+ kind: "lambda-image",
51
+ declaredBy: id,
52
+ docker: props.docker
53
+ });
38
54
  return {
39
55
  code: Code.fromEcrImage(props.ecrRepository, {
40
56
  tagOrDigest: param.valueAsString
@@ -0,0 +1,20 @@
1
+ import type { ComputeType } from "./interfaces/compute.js";
2
+ import type { EcsComputeProps } from "./computeEcsTypes.js";
3
+ import type { LambdaComputeProps } from "./computeLambda.js";
4
+ import type { Ec2ComputeProps } from "./computeEc2.js";
5
+ type KeysOfUnion<T> = T extends unknown ? keyof T : never;
6
+ type AllComputePropKeys = keyof EcsComputeProps | KeysOfUnion<LambdaComputeProps> | keyof Ec2ComputeProps;
7
+ export interface ComputeWarnGroup {
8
+ properties: string[];
9
+ applicableTypes: ComputeType[];
10
+ typeName: string;
11
+ }
12
+ export declare const COMPUTE_WARN_GROUPS: readonly ComputeWarnGroup[];
13
+ /**
14
+ * Runtime key set for tests — derived from the compiled guard, never
15
+ * re-listed (typescript-standards § compile-time guards).
16
+ */
17
+ export declare const ALL_COMPUTE_PROP_KEYS: AllComputePropKeys[];
18
+ export declare const CONTAINER_LAMBDA_ONLY_PROPS: string[];
19
+ export declare const CODE_LAMBDA_ONLY_PROPS: string[];
20
+ export {};
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Compile-time lockstep guard for `validateComputeProps`'s ignored-prop
3
+ * warnings. The Record over the full top-level key union rejects BOTH a
4
+ * missing key (a new prop added to any compute props interface fails TS2741
5
+ * here until classified) and a stale extra key (a removed/renamed prop fails
6
+ * TS2353). `"all"` means the prop is legitimate on every compute type and
7
+ * never warns.
8
+ */
9
+ const COMPUTE_PROP_APPLICABILITY = {
10
+ type: "all",
11
+ vpc: "all",
12
+ appName: ["ecs", "lambda"],
13
+ docker: ["ecs", "lambda"],
14
+ ecrRepository: ["ecs", "lambda"],
15
+ cluster: ["ecs"],
16
+ services: ["ecs"],
17
+ alertsTopic: ["ecs"],
18
+ applicationId: ["ecs"],
19
+ deployment: ["lambda"],
20
+ functionUrl: ["lambda"],
21
+ handler: ["lambda"],
22
+ runtime: ["lambda"],
23
+ code: ["lambda"],
24
+ image: ["lambda"],
25
+ timeout: ["lambda"],
26
+ memorySize: ["lambda"],
27
+ ephemeralStorageSize: ["lambda"],
28
+ architecture: ["lambda"],
29
+ description: ["lambda"],
30
+ roleDescription: ["lambda"],
31
+ inlinePolicy: ["lambda"],
32
+ environment: ["lambda"],
33
+ secrets: ["lambda"],
34
+ ssmSecretsPath: ["lambda"],
35
+ secretsImport: ["lambda"],
36
+ connections: ["lambda"],
37
+ instanceType: ["ec2"],
38
+ ssh: ["ec2"],
39
+ userData: ["ec2"],
40
+ machineImage: ["ec2"],
41
+ minCapacity: ["ec2"],
42
+ maxCapacity: ["ec2"],
43
+ spotCapacityPercentage: ["ec2"]
44
+ };
45
+ const TYPE_LABELS = {
46
+ ecs: "ECS",
47
+ lambda: "Lambda",
48
+ ec2: "EC2"
49
+ };
50
+ export const COMPUTE_WARN_GROUPS = (() => {
51
+ const groups = new Map();
52
+ for (const [prop, applicability] of Object.entries(COMPUTE_PROP_APPLICABILITY)) {
53
+ if (applicability === "all")
54
+ continue;
55
+ const key = applicability.join("+");
56
+ const existing = groups.get(key);
57
+ if (existing !== undefined) {
58
+ existing.properties.push(prop);
59
+ }
60
+ else {
61
+ groups.set(key, {
62
+ properties: [prop],
63
+ applicableTypes: [...applicability],
64
+ typeName: applicability.map((t) => TYPE_LABELS[t]).join(" or ") + " compute"
65
+ });
66
+ }
67
+ }
68
+ return [...groups.values()];
69
+ })();
70
+ /**
71
+ * Runtime key set for tests — derived from the compiled guard, never
72
+ * re-listed (typescript-standards § compile-time guards).
73
+ */
74
+ export const ALL_COMPUTE_PROP_KEYS = Object.keys(COMPUTE_PROP_APPLICABILITY);
75
+ const LAMBDA_DEPLOYMENT_APPLICABILITY = {
76
+ ecrRepository: "container",
77
+ docker: "container",
78
+ image: "container",
79
+ code: "code",
80
+ handler: "code",
81
+ runtime: "code"
82
+ };
83
+ const deploymentProps = (d) => Object.entries(LAMBDA_DEPLOYMENT_APPLICABILITY)
84
+ .filter(([, v]) => v === d)
85
+ .map(([k]) => k);
86
+ export const CONTAINER_LAMBDA_ONLY_PROPS = deploymentProps("container");
87
+ export const CODE_LAMBDA_ONLY_PROPS = deploymentProps("code");
@@ -28,7 +28,7 @@ export declare const DATABASE_ENGINE_CONFIG: Record<DatabaseEngine, {
28
28
  defaultUsername: string;
29
29
  sslParameters: Record<string, string>;
30
30
  }>;
31
- export declare function getAuroraClusterEngine(engine: DatabaseEngine): IClusterEngine;
31
+ export declare function getAuroraClusterEngine(engine: DatabaseEngine, engineVersion?: string): IClusterEngine;
32
32
  export declare function getInstanceEngine(engine: DatabaseEngine): IInstanceEngine;
33
33
  export declare function getEngineConfig(engine: DatabaseEngine): EngineConfig;
34
34
  /**
@@ -59,6 +59,24 @@ export interface AuroraDatabaseProps extends BaseDatabaseProps {
59
59
  databaseEngine?: DatabaseEngine;
60
60
  /** Overrides databaseEngine if provided */
61
61
  engine?: IClusterEngine;
62
+ /**
63
+ * Aurora PostgreSQL version to build the engine at, e.g. `"16.6"`. Overrides
64
+ * the `databaseEngine` default version; ignored when `engine` is supplied.
65
+ * Required `>= 16.3` when `serverlessV2MinCapacity` is 0 (auto-pause floor).
66
+ */
67
+ engineVersion?: string;
68
+ /** Physical cluster identifier. Lower-cased; falls back to a derived name. */
69
+ clusterIdentifier?: string;
70
+ /** Physical name of the generated master-credentials secret. */
71
+ credentialsSecretName?: string;
72
+ /** Serverless-v2 floor in ACU. 0 enables scale-to-zero (auto-pause). */
73
+ serverlessV2MinCapacity?: number;
74
+ /** Serverless-v2 ceiling in ACU. */
75
+ serverlessV2MaxCapacity?: number;
76
+ /** Idle duration before an idle serverless-v2 cluster auto-pauses to 0 ACU. */
77
+ serverlessV2AutoPauseDuration?: Duration;
78
+ /** Enables the RDS Data API (HTTP endpoint) on the cluster. */
79
+ enableDataApi?: boolean;
62
80
  writer?: AuroraWriterConfig;
63
81
  readers?: AuroraReadersConfig | false;
64
82
  proxy?: ProxyConfig | false;
@@ -57,8 +57,19 @@ const INSTANCE_ENGINE_FACTORIES = {
57
57
  });
58
58
  }
59
59
  };
60
- export function getAuroraClusterEngine(engine) {
61
- return AURORA_ENGINE_FACTORIES[engine]();
60
+ export function getAuroraClusterEngine(engine, engineVersion) {
61
+ if (engineVersion === undefined) {
62
+ return AURORA_ENGINE_FACTORIES[engine]();
63
+ }
64
+ // The auto-pause version floor is enforced in RdsAurora, alongside the
65
+ // min-capacity knob that requires it — not here.
66
+ if (engine !== "postgresql") {
67
+ throw new Error(`engineVersion override is only supported for postgresql Aurora clusters (received "${engine}")`);
68
+ }
69
+ const majorVersion = engineVersion.split(".")[0] ?? "16";
70
+ return DatabaseClusterEngine.auroraPostgres({
71
+ version: AuroraPostgresEngineVersion.of(engineVersion, majorVersion)
72
+ });
62
73
  }
63
74
  export function getInstanceEngine(engine) {
64
75
  return INSTANCE_ENGINE_FACTORIES[engine]();
@@ -346,7 +357,8 @@ export class RelationalDatabase extends Construct {
346
357
  }
347
358
  addAurora(props) {
348
359
  const resolvedDatabaseEngine = props.databaseEngine ?? "postgresql";
349
- const resolvedEngine = props.engine ?? getAuroraClusterEngine(resolvedDatabaseEngine);
360
+ const resolvedEngine = props.engine ??
361
+ getAuroraClusterEngine(resolvedDatabaseEngine, props.engineVersion);
350
362
  const engineConfig = getEngineConfig(resolvedDatabaseEngine);
351
363
  if (props.publiclyAccessible) {
352
364
  FjallLogger.warn(`Aurora database '${props.databaseName}' is configured with public accessibility. ` +
@@ -356,7 +368,14 @@ export class RelationalDatabase extends Construct {
356
368
  vpc: props.vpc,
357
369
  databaseName: props.databaseName,
358
370
  engine: resolvedEngine,
371
+ engineVersion: props.engineVersion,
359
372
  engineConfig,
373
+ clusterIdentifier: props.clusterIdentifier,
374
+ credentialsSecretName: props.credentialsSecretName,
375
+ serverlessV2MinCapacity: props.serverlessV2MinCapacity,
376
+ serverlessV2MaxCapacity: props.serverlessV2MaxCapacity,
377
+ serverlessV2AutoPauseDuration: props.serverlessV2AutoPauseDuration,
378
+ enableDataApi: props.enableDataApi,
360
379
  writer: props.writer,
361
380
  readers: props.readers,
362
381
  proxy: props.proxy,