@fjall/components-infrastructure 2.31.1 → 2.33.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 (27) hide show
  1. package/dist/lib/app.d.ts +27 -1
  2. package/dist/lib/app.js +35 -4
  3. package/dist/lib/patterns/aws/database.d.ts +19 -1
  4. package/dist/lib/patterns/aws/database.js +22 -3
  5. package/dist/lib/patterns/aws/devSubstrate.d.ts +85 -0
  6. package/dist/lib/patterns/aws/devSubstrate.js +390 -0
  7. package/dist/lib/patterns/aws/devSubstrate.waker.source.cjs +13 -0
  8. package/dist/lib/patterns/aws/interfaces/pattern.d.ts +3 -1
  9. package/dist/lib/resources/aws/compute/applicationLoadBalancer.d.ts +17 -0
  10. package/dist/lib/resources/aws/compute/applicationLoadBalancer.js +23 -0
  11. package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +4 -10
  12. package/dist/lib/resources/aws/compute/ecsNetworking.js +13 -48
  13. package/dist/lib/resources/aws/compute/ecsRoles.d.ts +25 -0
  14. package/dist/lib/resources/aws/compute/ecsRoles.js +39 -20
  15. package/dist/lib/resources/aws/compute/hostHeaderListenerRule.d.ts +23 -0
  16. package/dist/lib/resources/aws/compute/hostHeaderListenerRule.js +59 -0
  17. package/dist/lib/resources/aws/compute/lambdaAlbTarget.d.ts +17 -0
  18. package/dist/lib/resources/aws/compute/lambdaAlbTarget.js +18 -0
  19. package/dist/lib/resources/aws/compute/listenerRouting.d.ts +21 -0
  20. package/dist/lib/resources/aws/compute/listenerRouting.js +20 -0
  21. package/dist/lib/resources/aws/database/rdsAurora.d.ts +16 -0
  22. package/dist/lib/resources/aws/database/rdsAurora.js +57 -3
  23. package/dist/lib/resources/aws/database/rdsDefaults.d.ts +28 -0
  24. package/dist/lib/resources/aws/database/rdsDefaults.js +28 -0
  25. package/dist/lib/utils/devSubstrateTags.d.ts +22 -0
  26. package/dist/lib/utils/devSubstrateTags.js +22 -0
  27. package/package.json +5 -5
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
  /**
@@ -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,
@@ -0,0 +1,85 @@
1
+ import { Construct } from "constructs";
2
+ import type App from "../../app.js";
3
+ /**
4
+ * Props for the internal `DevSubstrate` construct.
5
+ *
6
+ * Deliberately standalone — NOT a member of the `IPatternProps` union and NOT
7
+ * registered in `PATTERN_TYPE_VALUES` / the pattern registry / `PatternFactory`.
8
+ * The substrate is Fjall-managed dev infrastructure, never offered on the
9
+ * customer create/scaffold surface (design R5). Increments A2–A7 extend this
10
+ * interface (S3→SQS, compute/network, domain, delegation, `phase`) as each lands.
11
+ */
12
+ export interface IDevSubstrateProps {
13
+ /**
14
+ * Real customer application identifier, surfaced as the `fjall:app` reaper
15
+ * tag. Distinct from the `fjall-dev-<app>` App identity that drives the
16
+ * substrate's stack names.
17
+ */
18
+ readonly appId: string;
19
+ /**
20
+ * Kebab-cased customer app name used to pin the substrate's physical resource
21
+ * names inside the `fjall-dev-*` fence (cluster identifier, master secret) so
22
+ * the dev-tier IAM grants match. Single-sourced with the `fjall-dev-<app>` App
23
+ * identity (design VERDICT-3).
24
+ */
25
+ readonly appKebab: string;
26
+ /**
27
+ * Aurora PostgreSQL engine version override (default `DEV_AURORA_DEFAULTS`).
28
+ * Must be `>= 16.3` — the shared cluster runs `serverlessV2MinCapacity` 0,
29
+ * which needs an auto-pause-capable engine (RdsAurora rejects lower at synth).
30
+ */
31
+ readonly engineVersion?: string;
32
+ /**
33
+ * When `true`, adopt an already-existing slot image ECR repository
34
+ * (`fjall-dev-<appKebab>-slots`) by reference instead of creating one — an
35
+ * import only, emitting no CloudFormation resource (and therefore untaggable).
36
+ * Defaults to `false`: create and fence-tag the repo. Set on a redeploy into an
37
+ * account where the repo was provisioned out-of-band.
38
+ */
39
+ readonly adoptSlotEcr?: boolean;
40
+ /**
41
+ * Deploy phase for the R2 two-step cert-hang guard. `"zone"` synthesises the
42
+ * `dev.<appDomain>` child zone + cross-account NS delegation only; `"full"`
43
+ * additionally builds the wildcard cert + :443 listener (A6) — deploy that
44
+ * only after the delegated NS has propagated, else ACM DNS-validation hangs.
45
+ * Defaults to `"full"`. Inert unless `domain` is set.
46
+ */
47
+ readonly phase?: "zone" | "full";
48
+ /**
49
+ * Public domain the substrate hangs a `dev.<appDomain>` child zone under. When
50
+ * omitted, the substrate stands up no zone/cert (early milestone — G2 reaches
51
+ * slots via `curl --resolve`).
52
+ *
53
+ * `parentDelegationRoleArn`, when present, wires the same-org **Case-1** in-stack
54
+ * NS delegation (design VD2-a): the child stack's `CrossAccountZoneDelegationRecord`
55
+ * assumes the parent account's `DelegationRole` by LITERAL ARN and UPSERTs the
56
+ * child NS into the parent zone. Omit it for a plain externally-owned parent
57
+ * (Case 3 — manual one-off delegation) or a connected different-org parent
58
+ * (Case 2 — worker UPSERT, B3b, deferred): the substrate then emits only the
59
+ * zone + its NS export for out-of-band delegation.
60
+ */
61
+ readonly domain?: {
62
+ readonly appDomain: string;
63
+ readonly parentDelegationRoleArn?: string;
64
+ };
65
+ }
66
+ /**
67
+ * Shared per-app substrate for ephemeral dev/preview slots (fast dev-envs
68
+ * Phase 3). Stands up the long-lived infrastructure a customer dev team's slots
69
+ * share — Aurora (A1), an S3→SQS drop (A2), an ECS cluster + slot SG (A2.5), an
70
+ * ALB with host-header routing (A3) fronted by a Lambda waker (A4), a delegated
71
+ * child zone (A5) and a wildcard cert (A6) — fenced to the development account.
72
+ *
73
+ * Instantiated ONLY via the internal `App.addDevSubstrate(props)`, which scopes
74
+ * it under the default compute stack so it actually deploys (design VF2). Every
75
+ * resource it creates carries `fjall:dev = "true"` (the IAM fence tag) and
76
+ * `fjall:app = <appId>` (the reaper tag) — including resources created through
77
+ * the app factories in sibling stacks, which are tagged explicitly since the
78
+ * `Tags.of(this)` aspect only reaches this construct's own scope subtree.
79
+ */
80
+ export declare class DevSubstrate extends Construct {
81
+ #private;
82
+ private readonly app;
83
+ private readonly appId;
84
+ constructor(scope: Construct, id: string, app: App, props: IDevSubstrateProps);
85
+ }