@aws/nx-plugin 0.80.1 → 0.81.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.
@@ -376,6 +376,7 @@ import {
376
376
  } from 'aws-cdk-lib/aws-iam';
377
377
  import { IUserPool } from 'aws-cdk-lib/aws-cognito';
378
378
  import {
379
+ ApiIntegrations,
379
380
  IntegrationBuilder,
380
381
  RestApiIntegration,
381
382
  } from '../../core/api/utils.js';
@@ -392,7 +393,7 @@ type Operations = Procedures<AppRouter>;
392
393
  * @template TIntegrations - Map of operation names to their integrations
393
394
  */
394
395
  export interface TestApiProps<
395
- TIntegrations extends Record<Operations, RestApiIntegration>,
396
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
396
397
  > {
397
398
  /**
398
399
  * Map of operation names to their API Gateway integrations
@@ -412,7 +413,7 @@ export interface TestApiProps<
412
413
  * @template TIntegrations - Map of operation names to their integrations
413
414
  */
414
415
  export class TestApi<
415
- TIntegrations extends Record<Operations, RestApiIntegration>,
416
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
416
417
  > extends RestApi<Operations, TIntegrations> {
417
418
  /**
418
419
  * Creates default integrations for all operations, which implement each operation as
@@ -423,8 +424,9 @@ export class TestApi<
423
424
  */
424
425
  public static defaultIntegrations = (scope: Construct) => {
425
426
  return IntegrationBuilder.rest({
427
+ pattern: 'isolated',
426
428
  operations: routerToOperations(appRouter),
427
- defaultIntegrationOptions: {
429
+ defaultIntegrationOptions: <FunctionProps>{
428
430
  runtime: Runtime.NODEJS_LATEST,
429
431
  handler: 'index.handler',
430
432
  code: Code.fromAsset(
@@ -440,7 +442,7 @@ export class TestApi<
440
442
  environment: {
441
443
  AWS_CONNECTION_REUSE_ENABLED: '1',
442
444
  },
443
- } satisfies FunctionProps,
445
+ },
444
446
  buildDefaultIntegration: (op, props: FunctionProps) => {
445
447
  const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
446
448
  return {
@@ -565,6 +567,7 @@ import { HttpUserPoolAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers
565
567
  import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
566
568
  import { IUserPool, IUserPoolClient } from 'aws-cdk-lib/aws-cognito';
567
569
  import {
570
+ ApiIntegrations,
568
571
  HttpApiIntegration,
569
572
  IntegrationBuilder,
570
573
  } from '../../core/api/utils.js';
@@ -581,7 +584,7 @@ type Operations = Procedures<AppRouter>;
581
584
  * @template TIntegrations - Map of operation names to their integrations
582
585
  */
583
586
  export interface TestApiProps<
584
- TIntegrations extends Record<Operations, HttpApiIntegration>,
587
+ TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
585
588
  > {
586
589
  /**
587
590
  * Map of operation names to their API Gateway integrations
@@ -602,7 +605,7 @@ export interface TestApiProps<
602
605
  * @template TIntegrations - Map of operation names to their integrations
603
606
  */
604
607
  export class TestApi<
605
- TIntegrations extends Record<Operations, HttpApiIntegration>,
608
+ TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
606
609
  > extends HttpApi<Operations, TIntegrations> {
607
610
  /**
608
611
  * Creates default integrations for all operations, which implement each operation as
@@ -613,8 +616,9 @@ export class TestApi<
613
616
  */
614
617
  public static defaultIntegrations = (scope: Construct) => {
615
618
  return IntegrationBuilder.http({
619
+ pattern: 'isolated',
616
620
  operations: routerToOperations(appRouter),
617
- defaultIntegrationOptions: {
621
+ defaultIntegrationOptions: <FunctionProps>{
618
622
  runtime: Runtime.NODEJS_LATEST,
619
623
  handler: 'index.handler',
620
624
  code: Code.fromAsset(
@@ -630,7 +634,7 @@ export class TestApi<
630
634
  environment: {
631
635
  AWS_CONNECTION_REUSE_ENABLED: '1',
632
636
  },
633
- } satisfies FunctionProps,
637
+ },
634
638
  buildDefaultIntegration: (op, props: FunctionProps) => {
635
639
  const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
636
640
  return {
@@ -768,6 +772,7 @@ import {
768
772
  AnyPrincipal,
769
773
  } from 'aws-cdk-lib/aws-iam';
770
774
  import {
775
+ ApiIntegrations,
771
776
  IntegrationBuilder,
772
777
  RestApiIntegration,
773
778
  } from '../../core/api/utils.js';
@@ -784,7 +789,7 @@ type Operations = Procedures<AppRouter>;
784
789
  * @template TIntegrations - Map of operation names to their integrations
785
790
  */
786
791
  export interface TestApiProps<
787
- TIntegrations extends Record<Operations, RestApiIntegration>,
792
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
788
793
  > {
789
794
  /**
790
795
  * Map of operation names to their API Gateway integrations
@@ -798,7 +803,7 @@ export interface TestApiProps<
798
803
  * @template TIntegrations - Map of operation names to their integrations
799
804
  */
800
805
  export class TestApi<
801
- TIntegrations extends Record<Operations, RestApiIntegration>,
806
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
802
807
  > extends RestApi<Operations, TIntegrations> {
803
808
  /**
804
809
  * Creates default integrations for all operations, which implement each operation as
@@ -809,8 +814,9 @@ export class TestApi<
809
814
  */
810
815
  public static defaultIntegrations = (scope: Construct) => {
811
816
  return IntegrationBuilder.rest({
817
+ pattern: 'isolated',
812
818
  operations: routerToOperations(appRouter),
813
- defaultIntegrationOptions: {
819
+ defaultIntegrationOptions: <FunctionProps>{
814
820
  runtime: Runtime.NODEJS_LATEST,
815
821
  handler: 'index.handler',
816
822
  code: Code.fromAsset(
@@ -826,7 +832,7 @@ export class TestApi<
826
832
  environment: {
827
833
  AWS_CONNECTION_REUSE_ENABLED: '1',
828
834
  },
829
- } satisfies FunctionProps,
835
+ },
830
836
  buildDefaultIntegration: (op, props: FunctionProps) => {
831
837
  const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
832
838
  return {
@@ -946,6 +952,7 @@ import {
946
952
  } from 'aws-cdk-lib/aws-apigatewayv2';
947
953
  import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
948
954
  import {
955
+ ApiIntegrations,
949
956
  HttpApiIntegration,
950
957
  IntegrationBuilder,
951
958
  } from '../../core/api/utils.js';
@@ -962,7 +969,7 @@ type Operations = Procedures<AppRouter>;
962
969
  * @template TIntegrations - Map of operation names to their integrations
963
970
  */
964
971
  export interface TestApiProps<
965
- TIntegrations extends Record<Operations, HttpApiIntegration>,
972
+ TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
966
973
  > {
967
974
  /**
968
975
  * Map of operation names to their API Gateway integrations
@@ -976,7 +983,7 @@ export interface TestApiProps<
976
983
  * @template TIntegrations - Map of operation names to their integrations
977
984
  */
978
985
  export class TestApi<
979
- TIntegrations extends Record<Operations, HttpApiIntegration>,
986
+ TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
980
987
  > extends HttpApi<Operations, TIntegrations> {
981
988
  /**
982
989
  * Creates default integrations for all operations, which implement each operation as
@@ -987,8 +994,9 @@ export class TestApi<
987
994
  */
988
995
  public static defaultIntegrations = (scope: Construct) => {
989
996
  return IntegrationBuilder.http({
997
+ pattern: 'isolated',
990
998
  operations: routerToOperations(appRouter),
991
- defaultIntegrationOptions: {
999
+ defaultIntegrationOptions: <FunctionProps>{
992
1000
  runtime: Runtime.NODEJS_LATEST,
993
1001
  handler: 'index.handler',
994
1002
  code: Code.fromAsset(
@@ -1004,7 +1012,7 @@ export class TestApi<
1004
1012
  environment: {
1005
1013
  AWS_CONNECTION_REUSE_ENABLED: '1',
1006
1014
  },
1007
- } satisfies FunctionProps,
1015
+ },
1008
1016
  buildDefaultIntegration: (op, props: FunctionProps) => {
1009
1017
  const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
1010
1018
  return {
@@ -1090,7 +1098,11 @@ export class TestApi<
1090
1098
  exports[`trpc backend generator > should set up shared constructs for http > http-api.ts 1`] = `
1091
1099
  "import { Construct } from 'constructs';
1092
1100
  import { RuntimeConfig } from '../runtime-config.js';
1093
- import { HttpApiIntegration, OperationDetails } from './utils.js';
1101
+ import {
1102
+ ApiIntegrations,
1103
+ HttpApiIntegration,
1104
+ OperationDetails,
1105
+ } from './utils.js';
1094
1106
  import { CfnOutput } from 'aws-cdk-lib';
1095
1107
  import {
1096
1108
  HttpApi as _HttpApi,
@@ -1109,7 +1121,7 @@ import { suppressRules } from '../checkov.js';
1109
1121
  * @template TOperation - String literal type representing operation names
1110
1122
  */
1111
1123
  export interface HttpApiProps<
1112
- TIntegrations extends Record<TOperation, HttpApiIntegration>,
1124
+ TIntegrations extends ApiIntegrations<TOperation, HttpApiIntegration>,
1113
1125
  TOperation extends string,
1114
1126
  > extends _HttpApiProps {
1115
1127
  /**
@@ -1139,7 +1151,7 @@ export interface HttpApiProps<
1139
1151
  */
1140
1152
  export class HttpApi<
1141
1153
  TOperation extends string,
1142
- TIntegrations extends Record<TOperation, HttpApiIntegration>,
1154
+ TIntegrations extends ApiIntegrations<TOperation, HttpApiIntegration>,
1143
1155
  > extends Construct {
1144
1156
  /** The underlying CDK HttpApi instance */
1145
1157
  public readonly api: _HttpApi;
@@ -1189,16 +1201,32 @@ export class HttpApi<
1189
1201
  },
1190
1202
  });
1191
1203
 
1204
+ // Resolve the integration for a given operation.
1205
+ // If the operation has a dedicated integration, use it; otherwise fall back to $router.
1206
+ const resolveIntegration = (op: TOperation): HttpApiIntegration => {
1207
+ const record = integrations as Record<string, HttpApiIntegration>;
1208
+ if (op in record) {
1209
+ return record[op];
1210
+ }
1211
+ if ('$router' in record) {
1212
+ return record['$router'];
1213
+ }
1214
+ throw new Error(
1215
+ \`No integration found for operation '\${op}' and no $router integration available\`,
1216
+ );
1217
+ };
1218
+
1192
1219
  // Create API resources and methods for each operation
1193
1220
  (Object.entries(operations) as [TOperation, OperationDetails][]).map(
1194
1221
  ([op, details]) => {
1222
+ const integration = resolveIntegration(op);
1195
1223
  this.api.addRoutes({
1196
1224
  path: details.path.startsWith('/')
1197
1225
  ? details.path
1198
1226
  : \`/\${details.path}\`,
1199
1227
  methods: [details.method as HttpMethod],
1200
- integration: integrations[op].integration,
1201
- ...integrations[op].options,
1228
+ integration: integration.integration,
1229
+ ...integration.options,
1202
1230
  });
1203
1231
  },
1204
1232
  );
@@ -1241,6 +1269,7 @@ import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
1241
1269
  import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
1242
1270
  import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam';
1243
1271
  import {
1272
+ ApiIntegrations,
1244
1273
  HttpApiIntegration,
1245
1274
  IntegrationBuilder,
1246
1275
  } from '../../core/api/utils.js';
@@ -1257,7 +1286,7 @@ type Operations = Procedures<AppRouter>;
1257
1286
  * @template TIntegrations - Map of operation names to their integrations
1258
1287
  */
1259
1288
  export interface TestApiProps<
1260
- TIntegrations extends Record<Operations, HttpApiIntegration>,
1289
+ TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
1261
1290
  > {
1262
1291
  /**
1263
1292
  * Map of operation names to their API Gateway integrations
@@ -1271,7 +1300,7 @@ export interface TestApiProps<
1271
1300
  * @template TIntegrations - Map of operation names to their integrations
1272
1301
  */
1273
1302
  export class TestApi<
1274
- TIntegrations extends Record<Operations, HttpApiIntegration>,
1303
+ TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
1275
1304
  > extends HttpApi<Operations, TIntegrations> {
1276
1305
  /**
1277
1306
  * Creates default integrations for all operations, which implement each operation as
@@ -1282,8 +1311,9 @@ export class TestApi<
1282
1311
  */
1283
1312
  public static defaultIntegrations = (scope: Construct) => {
1284
1313
  return IntegrationBuilder.http({
1314
+ pattern: 'isolated',
1285
1315
  operations: routerToOperations(appRouter),
1286
- defaultIntegrationOptions: {
1316
+ defaultIntegrationOptions: <FunctionProps>{
1287
1317
  runtime: Runtime.NODEJS_LATEST,
1288
1318
  handler: 'index.handler',
1289
1319
  code: Code.fromAsset(
@@ -1299,7 +1329,7 @@ export class TestApi<
1299
1329
  environment: {
1300
1330
  AWS_CONNECTION_REUSE_ENABLED: '1',
1301
1331
  },
1302
- } satisfies FunctionProps,
1332
+ },
1303
1333
  buildDefaultIntegration: (op, props: FunctionProps) => {
1304
1334
  const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
1305
1335
  return {
@@ -1518,20 +1548,46 @@ export interface HttpApiIntegration {
1518
1548
  }
1519
1549
 
1520
1550
  /**
1521
- * Options for constructing an IntegrationBuilder
1551
+ * Common options shared by all IntegrationBuilder configurations
1522
1552
  */
1523
- export interface IntegrationBuilderProps<
1553
+ interface IntegrationBuilderPropsBase<
1524
1554
  TOperation extends string,
1525
- TBaseIntegration,
1526
1555
  TDefaultIntegrationProps extends object,
1527
- TDefaultIntegration extends TBaseIntegration,
1528
1556
  > {
1529
1557
  /** Map of operation names to their API path and HTTP method details */
1530
1558
  operations: Record<TOperation, OperationDetails>;
1531
1559
 
1532
1560
  /** Default configuration options for integrations */
1533
1561
  defaultIntegrationOptions: TDefaultIntegrationProps;
1562
+ }
1534
1563
 
1564
+ /**
1565
+ * Options for constructing an IntegrationBuilder with a shared integration pattern.
1566
+ * A single default integration is built once (for '$router') and reused for all operations.
1567
+ */
1568
+ interface SharedIntegrationBuilderProps<
1569
+ TOperation extends string,
1570
+ TDefaultIntegrationProps extends object,
1571
+ TDefaultIntegration,
1572
+ > extends IntegrationBuilderPropsBase<TOperation, TDefaultIntegrationProps> {
1573
+ pattern: 'shared';
1574
+ /** Function to create a default integration for the shared router */
1575
+ buildDefaultIntegration: (
1576
+ op: '$router',
1577
+ props: TDefaultIntegrationProps,
1578
+ ) => TDefaultIntegration;
1579
+ }
1580
+
1581
+ /**
1582
+ * Options for constructing an IntegrationBuilder with an isolated integration pattern.
1583
+ * A separate default integration is built per operation.
1584
+ */
1585
+ interface IsolatedIntegrationBuilderProps<
1586
+ TOperation extends string,
1587
+ TDefaultIntegrationProps extends object,
1588
+ TDefaultIntegration,
1589
+ > extends IntegrationBuilderPropsBase<TOperation, TDefaultIntegrationProps> {
1590
+ pattern: 'isolated';
1535
1591
  /** Function to create a default integration for an operation */
1536
1592
  buildDefaultIntegration: (
1537
1593
  op: TOperation,
@@ -1539,6 +1595,71 @@ export interface IntegrationBuilderProps<
1539
1595
  ) => TDefaultIntegration;
1540
1596
  }
1541
1597
 
1598
+ /**
1599
+ * Options for constructing an IntegrationBuilder
1600
+ */
1601
+ export type IntegrationBuilderProps<
1602
+ TOperation extends string,
1603
+ TBaseIntegration,
1604
+ TDefaultIntegrationProps extends object,
1605
+ TDefaultIntegration extends TBaseIntegration,
1606
+ > =
1607
+ | SharedIntegrationBuilderProps<
1608
+ TOperation,
1609
+ TDefaultIntegrationProps,
1610
+ TDefaultIntegration
1611
+ >
1612
+ | IsolatedIntegrationBuilderProps<
1613
+ TOperation,
1614
+ TDefaultIntegrationProps,
1615
+ TDefaultIntegration
1616
+ >;
1617
+
1618
+ /**
1619
+ * Extracts the IntegrationBuilderProps variant matching the given pattern.
1620
+ */
1621
+ type IntegrationBuilderPropsForPattern<
1622
+ TOperation extends string,
1623
+ TBaseIntegration,
1624
+ TDefaultIntegrationProps extends object,
1625
+ TDefaultIntegration extends TBaseIntegration,
1626
+ TPattern extends 'shared' | 'isolated',
1627
+ > = Extract<
1628
+ IntegrationBuilderProps<
1629
+ TOperation,
1630
+ TBaseIntegration,
1631
+ TDefaultIntegrationProps,
1632
+ TDefaultIntegration
1633
+ >,
1634
+ { pattern: TPattern }
1635
+ >;
1636
+
1637
+ /**
1638
+ * Resolves the default integration keys based on the pattern.
1639
+ * - 'shared': only '$router' is a default key
1640
+ * - 'isolated': all TOperation keys are default keys
1641
+ */
1642
+ type DefaultIntegrationKeys<
1643
+ TPattern extends 'shared' | 'isolated',
1644
+ TOperation extends string,
1645
+ > = TPattern extends 'shared' ? '$router' : TOperation;
1646
+
1647
+ /**
1648
+ * Shared integration record: $router is required, individual operations are optional.
1649
+ */
1650
+ type SharedIntegrations<TOperation extends string, TBaseIntegration> = {
1651
+ $router: TBaseIntegration;
1652
+ } & Partial<Record<TOperation, TBaseIntegration>>;
1653
+
1654
+ /**
1655
+ * Valid integration record types for an API.
1656
+ * Either all operations are provided (isolated), or $router is provided
1657
+ * with optional per-operation overrides (shared).
1658
+ */
1659
+ export type ApiIntegrations<TOperation extends string, TBaseIntegration> =
1660
+ | SharedIntegrations<TOperation, TBaseIntegration>
1661
+ | (Record<TOperation, TBaseIntegration> & { $router?: never });
1662
+
1542
1663
  /**
1543
1664
  * A builder class for creating API integrations with flexible configuration options.
1544
1665
  *
@@ -1547,16 +1668,18 @@ export interface IntegrationBuilderProps<
1547
1668
  *
1548
1669
  * @template TOperation - String literal type representing operation names
1549
1670
  * @template TBaseIntegration - Base type for all integrations
1550
- * @template TIntegrations - Record mapping operation names to their integrations
1671
+ * @template TIntegrations - Record mapping integration keys to their integrations
1551
1672
  * @template TDefaultIntegrationProps - Type for default integration properties
1552
1673
  * @template TDefaultIntegration - Type for default integration implementation
1674
+ * @template TPattern - The integration pattern ('shared' or 'isolated')
1553
1675
  */
1554
1676
  export class IntegrationBuilder<
1555
1677
  TOperation extends string,
1556
1678
  TBaseIntegration,
1557
- TIntegrations extends Record<TOperation, TBaseIntegration>,
1679
+ TIntegrations extends Record<string, TBaseIntegration>,
1558
1680
  TDefaultIntegrationProps extends object,
1559
1681
  TDefaultIntegration extends TBaseIntegration,
1682
+ TPattern extends 'shared' | 'isolated',
1560
1683
  > {
1561
1684
  /** Options for the integration builder */
1562
1685
  private options: IntegrationBuilderProps<
@@ -1574,23 +1697,25 @@ export class IntegrationBuilder<
1574
1697
  */
1575
1698
  public static http = <
1576
1699
  TOperation extends string,
1577
- TIntegrations extends Record<TOperation, TDefaultIntegration>,
1578
1700
  TDefaultIntegrationProps extends object,
1579
1701
  TDefaultIntegration extends HttpApiIntegration,
1702
+ TPattern extends 'shared' | 'isolated',
1580
1703
  >(
1581
- options: IntegrationBuilderProps<
1704
+ options: IntegrationBuilderPropsForPattern<
1582
1705
  TOperation,
1583
1706
  HttpApiIntegration,
1584
1707
  TDefaultIntegrationProps,
1585
- TDefaultIntegration
1708
+ TDefaultIntegration,
1709
+ TPattern
1586
1710
  >,
1587
1711
  ) => {
1588
1712
  return new IntegrationBuilder<
1589
1713
  TOperation,
1590
1714
  HttpApiIntegration,
1591
- TIntegrations,
1715
+ Record<DefaultIntegrationKeys<TPattern, TOperation>, TDefaultIntegration>,
1592
1716
  TDefaultIntegrationProps,
1593
- TDefaultIntegration
1717
+ TDefaultIntegration,
1718
+ TPattern
1594
1719
  >(options);
1595
1720
  };
1596
1721
 
@@ -1599,23 +1724,25 @@ export class IntegrationBuilder<
1599
1724
  */
1600
1725
  public static rest = <
1601
1726
  TOperation extends string,
1602
- TIntegrations extends Record<TOperation, TDefaultIntegration>,
1603
1727
  TDefaultIntegrationProps extends object,
1604
1728
  TDefaultIntegration extends RestApiIntegration,
1729
+ TPattern extends 'shared' | 'isolated',
1605
1730
  >(
1606
- options: IntegrationBuilderProps<
1731
+ options: IntegrationBuilderPropsForPattern<
1607
1732
  TOperation,
1608
1733
  RestApiIntegration,
1609
1734
  TDefaultIntegrationProps,
1610
- TDefaultIntegration
1735
+ TDefaultIntegration,
1736
+ TPattern
1611
1737
  >,
1612
1738
  ) => {
1613
1739
  return new IntegrationBuilder<
1614
1740
  TOperation,
1615
1741
  RestApiIntegration,
1616
- TIntegrations,
1742
+ Record<DefaultIntegrationKeys<TPattern, TOperation>, TDefaultIntegration>,
1617
1743
  TDefaultIntegrationProps,
1618
- TDefaultIntegration
1744
+ TDefaultIntegration,
1745
+ TPattern
1619
1746
  >(options);
1620
1747
  };
1621
1748
 
@@ -1640,13 +1767,20 @@ export class IntegrationBuilder<
1640
1767
  TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
1641
1768
  >(overrides: TOverrideIntegrations) {
1642
1769
  this.integrations = { ...this.integrations, ...overrides };
1643
- // Re-type to include the overridden integration types
1770
+ // Re-type to include the overridden integration types.
1771
+ // If all operations are overridden in a 'shared' pattern, drop '$router' from the type.
1772
+ type MergedIntegrations = [TOperation] extends [keyof TOverrideIntegrations]
1773
+ ? Omit<TIntegrations, keyof TOverrideIntegrations | '$router'> &
1774
+ TOverrideIntegrations
1775
+ : Omit<TIntegrations, keyof TOverrideIntegrations> &
1776
+ TOverrideIntegrations;
1644
1777
  return this as unknown as IntegrationBuilder<
1645
1778
  TOperation,
1646
1779
  TBaseIntegration,
1647
- Omit<TIntegrations, keyof TOverrideIntegrations> & TOverrideIntegrations,
1780
+ MergedIntegrations,
1648
1781
  TDefaultIntegrationProps,
1649
- TDefaultIntegration
1782
+ TDefaultIntegration,
1783
+ TPattern
1650
1784
  >;
1651
1785
  }
1652
1786
 
@@ -1672,24 +1806,39 @@ export class IntegrationBuilder<
1672
1806
  * 1. Including all custom overrides provided via withOverrides()
1673
1807
  * 2. Creating default integrations for any operations without custom overrides
1674
1808
  *
1675
- * @returns A complete map of operation names to their integrations
1809
+ * For the 'shared' pattern, a single router integration is built and assigned to '$router'.
1810
+ * For the 'isolated' pattern, each operation without an override gets its own integration.
1811
+ *
1812
+ * @returns A complete map of integration keys to their integrations
1676
1813
  */
1677
1814
  public build(): TIntegrations {
1815
+ const options = this.options;
1678
1816
  return {
1679
- ...this.integrations,
1680
1817
  ...Object.fromEntries(
1681
- (Object.keys(this.options.operations) as TOperation[])
1682
- .filter(
1683
- (op) => !this.integrations[op as keyof typeof this.integrations],
1684
- )
1685
- .map((op) => [
1686
- op,
1687
- this.options.buildDefaultIntegration(
1688
- op,
1689
- this.options.defaultIntegrationOptions,
1690
- ),
1691
- ]),
1818
+ options.pattern === 'shared'
1819
+ ? [
1820
+ [
1821
+ '$router',
1822
+ options.buildDefaultIntegration(
1823
+ '$router',
1824
+ options.defaultIntegrationOptions,
1825
+ ),
1826
+ ],
1827
+ ]
1828
+ : (Object.keys(options.operations) as TOperation[])
1829
+ .filter(
1830
+ (op) =>
1831
+ !this.integrations[op as keyof typeof this.integrations],
1832
+ )
1833
+ .map((op) => [
1834
+ op,
1835
+ options.buildDefaultIntegration(
1836
+ op,
1837
+ options.defaultIntegrationOptions,
1838
+ ),
1839
+ ]),
1692
1840
  ),
1841
+ ...this.integrations,
1693
1842
  } as unknown as TIntegrations;
1694
1843
  }
1695
1844
  }
@@ -1705,17 +1854,21 @@ import {
1705
1854
  Stage,
1706
1855
  } from 'aws-cdk-lib/aws-apigateway';
1707
1856
  import { RuntimeConfig } from '../runtime-config.js';
1708
- import { OperationDetails, RestApiIntegration } from './utils.js';
1857
+ import {
1858
+ ApiIntegrations,
1859
+ OperationDetails,
1860
+ RestApiIntegration,
1861
+ } from './utils.js';
1709
1862
  import { suppressRules } from '../checkov.js';
1710
1863
 
1711
1864
  /**
1712
1865
  * Properties for creating a RestApi construct.
1713
1866
  *
1714
- * @template TIntegrations - Record mapping operation names to their integrations
1867
+ * @template TIntegrations - Record mapping integration keys to their integrations
1715
1868
  * @template TOperation - String literal type representing operation names
1716
1869
  */
1717
1870
  export interface RestApiProps<
1718
- TIntegrations extends Record<TOperation, RestApiIntegration>,
1871
+ TIntegrations extends ApiIntegrations<TOperation, RestApiIntegration>,
1719
1872
  TOperation extends string,
1720
1873
  > extends _RestApiProps {
1721
1874
  /**
@@ -1727,7 +1880,7 @@ export interface RestApiProps<
1727
1880
  */
1728
1881
  readonly operations: Record<TOperation, OperationDetails>;
1729
1882
  /**
1730
- * Map of operation names to their API Gateway integrations
1883
+ * Map of integration keys to their API Gateway integrations
1731
1884
  */
1732
1885
  readonly integrations: TIntegrations;
1733
1886
  }
@@ -1741,16 +1894,16 @@ export interface RestApiProps<
1741
1894
  * - Integration with runtime configuration for client discovery
1742
1895
  *
1743
1896
  * @template TOperation - String literal type representing operation names
1744
- * @template TIntegrations - Record mapping operation names to their integrations
1897
+ * @template TIntegrations - Record mapping integration keys to their integrations
1745
1898
  */
1746
1899
  export class RestApi<
1747
1900
  TOperation extends string,
1748
- TIntegrations extends Record<TOperation, RestApiIntegration>,
1901
+ TIntegrations extends ApiIntegrations<TOperation, RestApiIntegration>,
1749
1902
  > extends Construct {
1750
1903
  /** The underlying CDK RestApi instance */
1751
1904
  public readonly api: _RestApi;
1752
1905
 
1753
- /** Map of operation names to their API Gateway integrations */
1906
+ /** Map of integration keys to their API Gateway integrations */
1754
1907
  public readonly integrations: TIntegrations;
1755
1908
 
1756
1909
  constructor(
@@ -1782,9 +1935,25 @@ export class RestApi<
1782
1935
  (c) => c instanceof Stage,
1783
1936
  );
1784
1937
 
1938
+ // Resolve the integration for a given operation.
1939
+ // If the operation has a dedicated integration, use it; otherwise fall back to $router.
1940
+ const resolveIntegration = (op: TOperation): RestApiIntegration => {
1941
+ const record = integrations as Record<string, RestApiIntegration>;
1942
+ if (op in record) {
1943
+ return record[op];
1944
+ }
1945
+ if ('$router' in record) {
1946
+ return record['$router'];
1947
+ }
1948
+ throw new Error(
1949
+ \`No integration found for operation '\${op}' and no $router integration available\`,
1950
+ );
1951
+ };
1952
+
1785
1953
  // Create API resources and methods for each operation
1786
1954
  (Object.entries(operations) as [TOperation, OperationDetails][]).map(
1787
1955
  ([op, details]) => {
1956
+ const integration = resolveIntegration(op);
1788
1957
  const resource = this.getOrCreateResource(
1789
1958
  this.api.root,
1790
1959
  (details.path.startsWith('/')
@@ -1794,8 +1963,8 @@ export class RestApi<
1794
1963
  );
1795
1964
  resource.addMethod(
1796
1965
  details.method,
1797
- integrations[op].integration,
1798
- integrations[op].options,
1966
+ integration.integration,
1967
+ integration.options,
1799
1968
  );
1800
1969
  },
1801
1970
  );
@@ -1856,6 +2025,7 @@ import {
1856
2025
  Grant,
1857
2026
  } from 'aws-cdk-lib/aws-iam';
1858
2027
  import {
2028
+ ApiIntegrations,
1859
2029
  IntegrationBuilder,
1860
2030
  RestApiIntegration,
1861
2031
  } from '../../core/api/utils.js';
@@ -1872,7 +2042,7 @@ type Operations = Procedures<AppRouter>;
1872
2042
  * @template TIntegrations - Map of operation names to their integrations
1873
2043
  */
1874
2044
  export interface TestApiProps<
1875
- TIntegrations extends Record<Operations, RestApiIntegration>,
2045
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
1876
2046
  > {
1877
2047
  /**
1878
2048
  * Map of operation names to their API Gateway integrations
@@ -1886,7 +2056,7 @@ export interface TestApiProps<
1886
2056
  * @template TIntegrations - Map of operation names to their integrations
1887
2057
  */
1888
2058
  export class TestApi<
1889
- TIntegrations extends Record<Operations, RestApiIntegration>,
2059
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
1890
2060
  > extends RestApi<Operations, TIntegrations> {
1891
2061
  /**
1892
2062
  * Creates default integrations for all operations, which implement each operation as
@@ -1897,8 +2067,9 @@ export class TestApi<
1897
2067
  */
1898
2068
  public static defaultIntegrations = (scope: Construct) => {
1899
2069
  return IntegrationBuilder.rest({
2070
+ pattern: 'isolated',
1900
2071
  operations: routerToOperations(appRouter),
1901
- defaultIntegrationOptions: {
2072
+ defaultIntegrationOptions: <FunctionProps>{
1902
2073
  runtime: Runtime.NODEJS_LATEST,
1903
2074
  handler: 'index.handler',
1904
2075
  code: Code.fromAsset(
@@ -1914,7 +2085,7 @@ export class TestApi<
1914
2085
  environment: {
1915
2086
  AWS_CONNECTION_REUSE_ENABLED: '1',
1916
2087
  },
1917
- } satisfies FunctionProps,
2088
+ },
1918
2089
  buildDefaultIntegration: (op, props: FunctionProps) => {
1919
2090
  const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
1920
2091
  return {
@@ -2139,20 +2310,46 @@ export interface HttpApiIntegration {
2139
2310
  }
2140
2311
 
2141
2312
  /**
2142
- * Options for constructing an IntegrationBuilder
2313
+ * Common options shared by all IntegrationBuilder configurations
2143
2314
  */
2144
- export interface IntegrationBuilderProps<
2315
+ interface IntegrationBuilderPropsBase<
2145
2316
  TOperation extends string,
2146
- TBaseIntegration,
2147
2317
  TDefaultIntegrationProps extends object,
2148
- TDefaultIntegration extends TBaseIntegration,
2149
2318
  > {
2150
2319
  /** Map of operation names to their API path and HTTP method details */
2151
2320
  operations: Record<TOperation, OperationDetails>;
2152
2321
 
2153
2322
  /** Default configuration options for integrations */
2154
2323
  defaultIntegrationOptions: TDefaultIntegrationProps;
2324
+ }
2155
2325
 
2326
+ /**
2327
+ * Options for constructing an IntegrationBuilder with a shared integration pattern.
2328
+ * A single default integration is built once (for '$router') and reused for all operations.
2329
+ */
2330
+ interface SharedIntegrationBuilderProps<
2331
+ TOperation extends string,
2332
+ TDefaultIntegrationProps extends object,
2333
+ TDefaultIntegration,
2334
+ > extends IntegrationBuilderPropsBase<TOperation, TDefaultIntegrationProps> {
2335
+ pattern: 'shared';
2336
+ /** Function to create a default integration for the shared router */
2337
+ buildDefaultIntegration: (
2338
+ op: '$router',
2339
+ props: TDefaultIntegrationProps,
2340
+ ) => TDefaultIntegration;
2341
+ }
2342
+
2343
+ /**
2344
+ * Options for constructing an IntegrationBuilder with an isolated integration pattern.
2345
+ * A separate default integration is built per operation.
2346
+ */
2347
+ interface IsolatedIntegrationBuilderProps<
2348
+ TOperation extends string,
2349
+ TDefaultIntegrationProps extends object,
2350
+ TDefaultIntegration,
2351
+ > extends IntegrationBuilderPropsBase<TOperation, TDefaultIntegrationProps> {
2352
+ pattern: 'isolated';
2156
2353
  /** Function to create a default integration for an operation */
2157
2354
  buildDefaultIntegration: (
2158
2355
  op: TOperation,
@@ -2160,6 +2357,71 @@ export interface IntegrationBuilderProps<
2160
2357
  ) => TDefaultIntegration;
2161
2358
  }
2162
2359
 
2360
+ /**
2361
+ * Options for constructing an IntegrationBuilder
2362
+ */
2363
+ export type IntegrationBuilderProps<
2364
+ TOperation extends string,
2365
+ TBaseIntegration,
2366
+ TDefaultIntegrationProps extends object,
2367
+ TDefaultIntegration extends TBaseIntegration,
2368
+ > =
2369
+ | SharedIntegrationBuilderProps<
2370
+ TOperation,
2371
+ TDefaultIntegrationProps,
2372
+ TDefaultIntegration
2373
+ >
2374
+ | IsolatedIntegrationBuilderProps<
2375
+ TOperation,
2376
+ TDefaultIntegrationProps,
2377
+ TDefaultIntegration
2378
+ >;
2379
+
2380
+ /**
2381
+ * Extracts the IntegrationBuilderProps variant matching the given pattern.
2382
+ */
2383
+ type IntegrationBuilderPropsForPattern<
2384
+ TOperation extends string,
2385
+ TBaseIntegration,
2386
+ TDefaultIntegrationProps extends object,
2387
+ TDefaultIntegration extends TBaseIntegration,
2388
+ TPattern extends 'shared' | 'isolated',
2389
+ > = Extract<
2390
+ IntegrationBuilderProps<
2391
+ TOperation,
2392
+ TBaseIntegration,
2393
+ TDefaultIntegrationProps,
2394
+ TDefaultIntegration
2395
+ >,
2396
+ { pattern: TPattern }
2397
+ >;
2398
+
2399
+ /**
2400
+ * Resolves the default integration keys based on the pattern.
2401
+ * - 'shared': only '$router' is a default key
2402
+ * - 'isolated': all TOperation keys are default keys
2403
+ */
2404
+ type DefaultIntegrationKeys<
2405
+ TPattern extends 'shared' | 'isolated',
2406
+ TOperation extends string,
2407
+ > = TPattern extends 'shared' ? '$router' : TOperation;
2408
+
2409
+ /**
2410
+ * Shared integration record: $router is required, individual operations are optional.
2411
+ */
2412
+ type SharedIntegrations<TOperation extends string, TBaseIntegration> = {
2413
+ $router: TBaseIntegration;
2414
+ } & Partial<Record<TOperation, TBaseIntegration>>;
2415
+
2416
+ /**
2417
+ * Valid integration record types for an API.
2418
+ * Either all operations are provided (isolated), or $router is provided
2419
+ * with optional per-operation overrides (shared).
2420
+ */
2421
+ export type ApiIntegrations<TOperation extends string, TBaseIntegration> =
2422
+ | SharedIntegrations<TOperation, TBaseIntegration>
2423
+ | (Record<TOperation, TBaseIntegration> & { $router?: never });
2424
+
2163
2425
  /**
2164
2426
  * A builder class for creating API integrations with flexible configuration options.
2165
2427
  *
@@ -2168,16 +2430,18 @@ export interface IntegrationBuilderProps<
2168
2430
  *
2169
2431
  * @template TOperation - String literal type representing operation names
2170
2432
  * @template TBaseIntegration - Base type for all integrations
2171
- * @template TIntegrations - Record mapping operation names to their integrations
2433
+ * @template TIntegrations - Record mapping integration keys to their integrations
2172
2434
  * @template TDefaultIntegrationProps - Type for default integration properties
2173
2435
  * @template TDefaultIntegration - Type for default integration implementation
2436
+ * @template TPattern - The integration pattern ('shared' or 'isolated')
2174
2437
  */
2175
2438
  export class IntegrationBuilder<
2176
2439
  TOperation extends string,
2177
2440
  TBaseIntegration,
2178
- TIntegrations extends Record<TOperation, TBaseIntegration>,
2441
+ TIntegrations extends Record<string, TBaseIntegration>,
2179
2442
  TDefaultIntegrationProps extends object,
2180
2443
  TDefaultIntegration extends TBaseIntegration,
2444
+ TPattern extends 'shared' | 'isolated',
2181
2445
  > {
2182
2446
  /** Options for the integration builder */
2183
2447
  private options: IntegrationBuilderProps<
@@ -2195,23 +2459,25 @@ export class IntegrationBuilder<
2195
2459
  */
2196
2460
  public static http = <
2197
2461
  TOperation extends string,
2198
- TIntegrations extends Record<TOperation, TDefaultIntegration>,
2199
2462
  TDefaultIntegrationProps extends object,
2200
2463
  TDefaultIntegration extends HttpApiIntegration,
2464
+ TPattern extends 'shared' | 'isolated',
2201
2465
  >(
2202
- options: IntegrationBuilderProps<
2466
+ options: IntegrationBuilderPropsForPattern<
2203
2467
  TOperation,
2204
2468
  HttpApiIntegration,
2205
2469
  TDefaultIntegrationProps,
2206
- TDefaultIntegration
2470
+ TDefaultIntegration,
2471
+ TPattern
2207
2472
  >,
2208
2473
  ) => {
2209
2474
  return new IntegrationBuilder<
2210
2475
  TOperation,
2211
2476
  HttpApiIntegration,
2212
- TIntegrations,
2477
+ Record<DefaultIntegrationKeys<TPattern, TOperation>, TDefaultIntegration>,
2213
2478
  TDefaultIntegrationProps,
2214
- TDefaultIntegration
2479
+ TDefaultIntegration,
2480
+ TPattern
2215
2481
  >(options);
2216
2482
  };
2217
2483
 
@@ -2220,23 +2486,25 @@ export class IntegrationBuilder<
2220
2486
  */
2221
2487
  public static rest = <
2222
2488
  TOperation extends string,
2223
- TIntegrations extends Record<TOperation, TDefaultIntegration>,
2224
2489
  TDefaultIntegrationProps extends object,
2225
2490
  TDefaultIntegration extends RestApiIntegration,
2491
+ TPattern extends 'shared' | 'isolated',
2226
2492
  >(
2227
- options: IntegrationBuilderProps<
2493
+ options: IntegrationBuilderPropsForPattern<
2228
2494
  TOperation,
2229
2495
  RestApiIntegration,
2230
2496
  TDefaultIntegrationProps,
2231
- TDefaultIntegration
2497
+ TDefaultIntegration,
2498
+ TPattern
2232
2499
  >,
2233
2500
  ) => {
2234
2501
  return new IntegrationBuilder<
2235
2502
  TOperation,
2236
2503
  RestApiIntegration,
2237
- TIntegrations,
2504
+ Record<DefaultIntegrationKeys<TPattern, TOperation>, TDefaultIntegration>,
2238
2505
  TDefaultIntegrationProps,
2239
- TDefaultIntegration
2506
+ TDefaultIntegration,
2507
+ TPattern
2240
2508
  >(options);
2241
2509
  };
2242
2510
 
@@ -2261,13 +2529,20 @@ export class IntegrationBuilder<
2261
2529
  TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
2262
2530
  >(overrides: TOverrideIntegrations) {
2263
2531
  this.integrations = { ...this.integrations, ...overrides };
2264
- // Re-type to include the overridden integration types
2532
+ // Re-type to include the overridden integration types.
2533
+ // If all operations are overridden in a 'shared' pattern, drop '$router' from the type.
2534
+ type MergedIntegrations = [TOperation] extends [keyof TOverrideIntegrations]
2535
+ ? Omit<TIntegrations, keyof TOverrideIntegrations | '$router'> &
2536
+ TOverrideIntegrations
2537
+ : Omit<TIntegrations, keyof TOverrideIntegrations> &
2538
+ TOverrideIntegrations;
2265
2539
  return this as unknown as IntegrationBuilder<
2266
2540
  TOperation,
2267
2541
  TBaseIntegration,
2268
- Omit<TIntegrations, keyof TOverrideIntegrations> & TOverrideIntegrations,
2542
+ MergedIntegrations,
2269
2543
  TDefaultIntegrationProps,
2270
- TDefaultIntegration
2544
+ TDefaultIntegration,
2545
+ TPattern
2271
2546
  >;
2272
2547
  }
2273
2548
 
@@ -2293,24 +2568,39 @@ export class IntegrationBuilder<
2293
2568
  * 1. Including all custom overrides provided via withOverrides()
2294
2569
  * 2. Creating default integrations for any operations without custom overrides
2295
2570
  *
2296
- * @returns A complete map of operation names to their integrations
2571
+ * For the 'shared' pattern, a single router integration is built and assigned to '$router'.
2572
+ * For the 'isolated' pattern, each operation without an override gets its own integration.
2573
+ *
2574
+ * @returns A complete map of integration keys to their integrations
2297
2575
  */
2298
2576
  public build(): TIntegrations {
2577
+ const options = this.options;
2299
2578
  return {
2300
- ...this.integrations,
2301
2579
  ...Object.fromEntries(
2302
- (Object.keys(this.options.operations) as TOperation[])
2303
- .filter(
2304
- (op) => !this.integrations[op as keyof typeof this.integrations],
2305
- )
2306
- .map((op) => [
2307
- op,
2308
- this.options.buildDefaultIntegration(
2309
- op,
2310
- this.options.defaultIntegrationOptions,
2311
- ),
2312
- ]),
2580
+ options.pattern === 'shared'
2581
+ ? [
2582
+ [
2583
+ '$router',
2584
+ options.buildDefaultIntegration(
2585
+ '$router',
2586
+ options.defaultIntegrationOptions,
2587
+ ),
2588
+ ],
2589
+ ]
2590
+ : (Object.keys(options.operations) as TOperation[])
2591
+ .filter(
2592
+ (op) =>
2593
+ !this.integrations[op as keyof typeof this.integrations],
2594
+ )
2595
+ .map((op) => [
2596
+ op,
2597
+ options.buildDefaultIntegration(
2598
+ op,
2599
+ options.defaultIntegrationOptions,
2600
+ ),
2601
+ ]),
2313
2602
  ),
2603
+ ...this.integrations,
2314
2604
  } as unknown as TIntegrations;
2315
2605
  }
2316
2606
  }