@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.
@@ -246,7 +246,11 @@ def test_main():
246
246
  exports[`fastapi project generator > should set up shared constructs for http > http-api.ts 1`] = `
247
247
  "import { Construct } from 'constructs';
248
248
  import { RuntimeConfig } from '../runtime-config.js';
249
- import { HttpApiIntegration, OperationDetails } from './utils.js';
249
+ import {
250
+ ApiIntegrations,
251
+ HttpApiIntegration,
252
+ OperationDetails,
253
+ } from './utils.js';
250
254
  import { CfnOutput } from 'aws-cdk-lib';
251
255
  import {
252
256
  HttpApi as _HttpApi,
@@ -265,7 +269,7 @@ import { suppressRules } from '../checkov.js';
265
269
  * @template TOperation - String literal type representing operation names
266
270
  */
267
271
  export interface HttpApiProps<
268
- TIntegrations extends Record<TOperation, HttpApiIntegration>,
272
+ TIntegrations extends ApiIntegrations<TOperation, HttpApiIntegration>,
269
273
  TOperation extends string,
270
274
  > extends _HttpApiProps {
271
275
  /**
@@ -295,7 +299,7 @@ export interface HttpApiProps<
295
299
  */
296
300
  export class HttpApi<
297
301
  TOperation extends string,
298
- TIntegrations extends Record<TOperation, HttpApiIntegration>,
302
+ TIntegrations extends ApiIntegrations<TOperation, HttpApiIntegration>,
299
303
  > extends Construct {
300
304
  /** The underlying CDK HttpApi instance */
301
305
  public readonly api: _HttpApi;
@@ -345,16 +349,32 @@ export class HttpApi<
345
349
  },
346
350
  });
347
351
 
352
+ // Resolve the integration for a given operation.
353
+ // If the operation has a dedicated integration, use it; otherwise fall back to $router.
354
+ const resolveIntegration = (op: TOperation): HttpApiIntegration => {
355
+ const record = integrations as Record<string, HttpApiIntegration>;
356
+ if (op in record) {
357
+ return record[op];
358
+ }
359
+ if ('$router' in record) {
360
+ return record['$router'];
361
+ }
362
+ throw new Error(
363
+ \`No integration found for operation '\${op}' and no $router integration available\`,
364
+ );
365
+ };
366
+
348
367
  // Create API resources and methods for each operation
349
368
  (Object.entries(operations) as [TOperation, OperationDetails][]).map(
350
369
  ([op, details]) => {
370
+ const integration = resolveIntegration(op);
351
371
  this.api.addRoutes({
352
372
  path: details.path.startsWith('/')
353
373
  ? details.path
354
374
  : \`/\${details.path}\`,
355
375
  methods: [details.method as HttpMethod],
356
- integration: integrations[op].integration,
357
- ...integrations[op].options,
376
+ integration: integration.integration,
377
+ ...integration.options,
358
378
  });
359
379
  },
360
380
  );
@@ -399,6 +419,7 @@ import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
399
419
  import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
400
420
  import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam';
401
421
  import {
422
+ ApiIntegrations,
402
423
  HttpApiIntegration,
403
424
  IntegrationBuilder,
404
425
  } from '../../core/api/utils.js';
@@ -414,7 +435,7 @@ import {
414
435
  * @template TIntegrations - Map of operation names to their integrations
415
436
  */
416
437
  export interface TestApiProps<
417
- TIntegrations extends Record<Operations, HttpApiIntegration>,
438
+ TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
418
439
  > {
419
440
  /**
420
441
  * Map of operation names to their API Gateway integrations
@@ -428,7 +449,7 @@ export interface TestApiProps<
428
449
  * @template TIntegrations - Map of operation names to their integrations
429
450
  */
430
451
  export class TestApi<
431
- TIntegrations extends Record<Operations, HttpApiIntegration>,
452
+ TIntegrations extends ApiIntegrations<Operations, HttpApiIntegration>,
432
453
  > extends HttpApi<Operations, TIntegrations> {
433
454
  /**
434
455
  * Creates default integrations for all operations, which implement each operation as
@@ -439,8 +460,9 @@ export class TestApi<
439
460
  */
440
461
  public static defaultIntegrations = (scope: Construct) => {
441
462
  return IntegrationBuilder.http({
463
+ pattern: 'isolated',
442
464
  operations: OPERATION_DETAILS,
443
- defaultIntegrationOptions: {
465
+ defaultIntegrationOptions: <FunctionProps>{
444
466
  runtime: Runtime.PYTHON_3_12,
445
467
  handler: 'run.sh',
446
468
  code: Code.fromAsset(
@@ -460,7 +482,7 @@ export class TestApi<
460
482
  AWS_LWA_INVOKE_MODE: 'buffered',
461
483
  AWS_LAMBDA_EXEC_WRAPPER: '/opt/bootstrap',
462
484
  },
463
- } satisfies FunctionProps,
485
+ },
464
486
  buildDefaultIntegration: (op, props: FunctionProps) => {
465
487
  const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
466
488
  const stack = Stack.of(scope);
@@ -616,20 +638,46 @@ export interface HttpApiIntegration {
616
638
  }
617
639
 
618
640
  /**
619
- * Options for constructing an IntegrationBuilder
641
+ * Common options shared by all IntegrationBuilder configurations
620
642
  */
621
- export interface IntegrationBuilderProps<
643
+ interface IntegrationBuilderPropsBase<
622
644
  TOperation extends string,
623
- TBaseIntegration,
624
645
  TDefaultIntegrationProps extends object,
625
- TDefaultIntegration extends TBaseIntegration,
626
646
  > {
627
647
  /** Map of operation names to their API path and HTTP method details */
628
648
  operations: Record<TOperation, OperationDetails>;
629
649
 
630
650
  /** Default configuration options for integrations */
631
651
  defaultIntegrationOptions: TDefaultIntegrationProps;
652
+ }
653
+
654
+ /**
655
+ * Options for constructing an IntegrationBuilder with a shared integration pattern.
656
+ * A single default integration is built once (for '$router') and reused for all operations.
657
+ */
658
+ interface SharedIntegrationBuilderProps<
659
+ TOperation extends string,
660
+ TDefaultIntegrationProps extends object,
661
+ TDefaultIntegration,
662
+ > extends IntegrationBuilderPropsBase<TOperation, TDefaultIntegrationProps> {
663
+ pattern: 'shared';
664
+ /** Function to create a default integration for the shared router */
665
+ buildDefaultIntegration: (
666
+ op: '$router',
667
+ props: TDefaultIntegrationProps,
668
+ ) => TDefaultIntegration;
669
+ }
632
670
 
671
+ /**
672
+ * Options for constructing an IntegrationBuilder with an isolated integration pattern.
673
+ * A separate default integration is built per operation.
674
+ */
675
+ interface IsolatedIntegrationBuilderProps<
676
+ TOperation extends string,
677
+ TDefaultIntegrationProps extends object,
678
+ TDefaultIntegration,
679
+ > extends IntegrationBuilderPropsBase<TOperation, TDefaultIntegrationProps> {
680
+ pattern: 'isolated';
633
681
  /** Function to create a default integration for an operation */
634
682
  buildDefaultIntegration: (
635
683
  op: TOperation,
@@ -637,6 +685,71 @@ export interface IntegrationBuilderProps<
637
685
  ) => TDefaultIntegration;
638
686
  }
639
687
 
688
+ /**
689
+ * Options for constructing an IntegrationBuilder
690
+ */
691
+ export type IntegrationBuilderProps<
692
+ TOperation extends string,
693
+ TBaseIntegration,
694
+ TDefaultIntegrationProps extends object,
695
+ TDefaultIntegration extends TBaseIntegration,
696
+ > =
697
+ | SharedIntegrationBuilderProps<
698
+ TOperation,
699
+ TDefaultIntegrationProps,
700
+ TDefaultIntegration
701
+ >
702
+ | IsolatedIntegrationBuilderProps<
703
+ TOperation,
704
+ TDefaultIntegrationProps,
705
+ TDefaultIntegration
706
+ >;
707
+
708
+ /**
709
+ * Extracts the IntegrationBuilderProps variant matching the given pattern.
710
+ */
711
+ type IntegrationBuilderPropsForPattern<
712
+ TOperation extends string,
713
+ TBaseIntegration,
714
+ TDefaultIntegrationProps extends object,
715
+ TDefaultIntegration extends TBaseIntegration,
716
+ TPattern extends 'shared' | 'isolated',
717
+ > = Extract<
718
+ IntegrationBuilderProps<
719
+ TOperation,
720
+ TBaseIntegration,
721
+ TDefaultIntegrationProps,
722
+ TDefaultIntegration
723
+ >,
724
+ { pattern: TPattern }
725
+ >;
726
+
727
+ /**
728
+ * Resolves the default integration keys based on the pattern.
729
+ * - 'shared': only '$router' is a default key
730
+ * - 'isolated': all TOperation keys are default keys
731
+ */
732
+ type DefaultIntegrationKeys<
733
+ TPattern extends 'shared' | 'isolated',
734
+ TOperation extends string,
735
+ > = TPattern extends 'shared' ? '$router' : TOperation;
736
+
737
+ /**
738
+ * Shared integration record: $router is required, individual operations are optional.
739
+ */
740
+ type SharedIntegrations<TOperation extends string, TBaseIntegration> = {
741
+ $router: TBaseIntegration;
742
+ } & Partial<Record<TOperation, TBaseIntegration>>;
743
+
744
+ /**
745
+ * Valid integration record types for an API.
746
+ * Either all operations are provided (isolated), or $router is provided
747
+ * with optional per-operation overrides (shared).
748
+ */
749
+ export type ApiIntegrations<TOperation extends string, TBaseIntegration> =
750
+ | SharedIntegrations<TOperation, TBaseIntegration>
751
+ | (Record<TOperation, TBaseIntegration> & { $router?: never });
752
+
640
753
  /**
641
754
  * A builder class for creating API integrations with flexible configuration options.
642
755
  *
@@ -645,16 +758,18 @@ export interface IntegrationBuilderProps<
645
758
  *
646
759
  * @template TOperation - String literal type representing operation names
647
760
  * @template TBaseIntegration - Base type for all integrations
648
- * @template TIntegrations - Record mapping operation names to their integrations
761
+ * @template TIntegrations - Record mapping integration keys to their integrations
649
762
  * @template TDefaultIntegrationProps - Type for default integration properties
650
763
  * @template TDefaultIntegration - Type for default integration implementation
764
+ * @template TPattern - The integration pattern ('shared' or 'isolated')
651
765
  */
652
766
  export class IntegrationBuilder<
653
767
  TOperation extends string,
654
768
  TBaseIntegration,
655
- TIntegrations extends Record<TOperation, TBaseIntegration>,
769
+ TIntegrations extends Record<string, TBaseIntegration>,
656
770
  TDefaultIntegrationProps extends object,
657
771
  TDefaultIntegration extends TBaseIntegration,
772
+ TPattern extends 'shared' | 'isolated',
658
773
  > {
659
774
  /** Options for the integration builder */
660
775
  private options: IntegrationBuilderProps<
@@ -672,23 +787,25 @@ export class IntegrationBuilder<
672
787
  */
673
788
  public static http = <
674
789
  TOperation extends string,
675
- TIntegrations extends Record<TOperation, TDefaultIntegration>,
676
790
  TDefaultIntegrationProps extends object,
677
791
  TDefaultIntegration extends HttpApiIntegration,
792
+ TPattern extends 'shared' | 'isolated',
678
793
  >(
679
- options: IntegrationBuilderProps<
794
+ options: IntegrationBuilderPropsForPattern<
680
795
  TOperation,
681
796
  HttpApiIntegration,
682
797
  TDefaultIntegrationProps,
683
- TDefaultIntegration
798
+ TDefaultIntegration,
799
+ TPattern
684
800
  >,
685
801
  ) => {
686
802
  return new IntegrationBuilder<
687
803
  TOperation,
688
804
  HttpApiIntegration,
689
- TIntegrations,
805
+ Record<DefaultIntegrationKeys<TPattern, TOperation>, TDefaultIntegration>,
690
806
  TDefaultIntegrationProps,
691
- TDefaultIntegration
807
+ TDefaultIntegration,
808
+ TPattern
692
809
  >(options);
693
810
  };
694
811
 
@@ -697,23 +814,25 @@ export class IntegrationBuilder<
697
814
  */
698
815
  public static rest = <
699
816
  TOperation extends string,
700
- TIntegrations extends Record<TOperation, TDefaultIntegration>,
701
817
  TDefaultIntegrationProps extends object,
702
818
  TDefaultIntegration extends RestApiIntegration,
819
+ TPattern extends 'shared' | 'isolated',
703
820
  >(
704
- options: IntegrationBuilderProps<
821
+ options: IntegrationBuilderPropsForPattern<
705
822
  TOperation,
706
823
  RestApiIntegration,
707
824
  TDefaultIntegrationProps,
708
- TDefaultIntegration
825
+ TDefaultIntegration,
826
+ TPattern
709
827
  >,
710
828
  ) => {
711
829
  return new IntegrationBuilder<
712
830
  TOperation,
713
831
  RestApiIntegration,
714
- TIntegrations,
832
+ Record<DefaultIntegrationKeys<TPattern, TOperation>, TDefaultIntegration>,
715
833
  TDefaultIntegrationProps,
716
- TDefaultIntegration
834
+ TDefaultIntegration,
835
+ TPattern
717
836
  >(options);
718
837
  };
719
838
 
@@ -738,13 +857,20 @@ export class IntegrationBuilder<
738
857
  TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
739
858
  >(overrides: TOverrideIntegrations) {
740
859
  this.integrations = { ...this.integrations, ...overrides };
741
- // Re-type to include the overridden integration types
860
+ // Re-type to include the overridden integration types.
861
+ // If all operations are overridden in a 'shared' pattern, drop '$router' from the type.
862
+ type MergedIntegrations = [TOperation] extends [keyof TOverrideIntegrations]
863
+ ? Omit<TIntegrations, keyof TOverrideIntegrations | '$router'> &
864
+ TOverrideIntegrations
865
+ : Omit<TIntegrations, keyof TOverrideIntegrations> &
866
+ TOverrideIntegrations;
742
867
  return this as unknown as IntegrationBuilder<
743
868
  TOperation,
744
869
  TBaseIntegration,
745
- Omit<TIntegrations, keyof TOverrideIntegrations> & TOverrideIntegrations,
870
+ MergedIntegrations,
746
871
  TDefaultIntegrationProps,
747
- TDefaultIntegration
872
+ TDefaultIntegration,
873
+ TPattern
748
874
  >;
749
875
  }
750
876
 
@@ -770,24 +896,39 @@ export class IntegrationBuilder<
770
896
  * 1. Including all custom overrides provided via withOverrides()
771
897
  * 2. Creating default integrations for any operations without custom overrides
772
898
  *
773
- * @returns A complete map of operation names to their integrations
899
+ * For the 'shared' pattern, a single router integration is built and assigned to '$router'.
900
+ * For the 'isolated' pattern, each operation without an override gets its own integration.
901
+ *
902
+ * @returns A complete map of integration keys to their integrations
774
903
  */
775
904
  public build(): TIntegrations {
905
+ const options = this.options;
776
906
  return {
777
- ...this.integrations,
778
907
  ...Object.fromEntries(
779
- (Object.keys(this.options.operations) as TOperation[])
780
- .filter(
781
- (op) => !this.integrations[op as keyof typeof this.integrations],
782
- )
783
- .map((op) => [
784
- op,
785
- this.options.buildDefaultIntegration(
786
- op,
787
- this.options.defaultIntegrationOptions,
788
- ),
789
- ]),
908
+ options.pattern === 'shared'
909
+ ? [
910
+ [
911
+ '$router',
912
+ options.buildDefaultIntegration(
913
+ '$router',
914
+ options.defaultIntegrationOptions,
915
+ ),
916
+ ],
917
+ ]
918
+ : (Object.keys(options.operations) as TOperation[])
919
+ .filter(
920
+ (op) =>
921
+ !this.integrations[op as keyof typeof this.integrations],
922
+ )
923
+ .map((op) => [
924
+ op,
925
+ options.buildDefaultIntegration(
926
+ op,
927
+ options.defaultIntegrationOptions,
928
+ ),
929
+ ]),
790
930
  ),
931
+ ...this.integrations,
791
932
  } as unknown as TIntegrations;
792
933
  }
793
934
  }
@@ -803,17 +944,21 @@ import {
803
944
  Stage,
804
945
  } from 'aws-cdk-lib/aws-apigateway';
805
946
  import { RuntimeConfig } from '../runtime-config.js';
806
- import { OperationDetails, RestApiIntegration } from './utils.js';
947
+ import {
948
+ ApiIntegrations,
949
+ OperationDetails,
950
+ RestApiIntegration,
951
+ } from './utils.js';
807
952
  import { suppressRules } from '../checkov.js';
808
953
 
809
954
  /**
810
955
  * Properties for creating a RestApi construct.
811
956
  *
812
- * @template TIntegrations - Record mapping operation names to their integrations
957
+ * @template TIntegrations - Record mapping integration keys to their integrations
813
958
  * @template TOperation - String literal type representing operation names
814
959
  */
815
960
  export interface RestApiProps<
816
- TIntegrations extends Record<TOperation, RestApiIntegration>,
961
+ TIntegrations extends ApiIntegrations<TOperation, RestApiIntegration>,
817
962
  TOperation extends string,
818
963
  > extends _RestApiProps {
819
964
  /**
@@ -825,7 +970,7 @@ export interface RestApiProps<
825
970
  */
826
971
  readonly operations: Record<TOperation, OperationDetails>;
827
972
  /**
828
- * Map of operation names to their API Gateway integrations
973
+ * Map of integration keys to their API Gateway integrations
829
974
  */
830
975
  readonly integrations: TIntegrations;
831
976
  }
@@ -839,16 +984,16 @@ export interface RestApiProps<
839
984
  * - Integration with runtime configuration for client discovery
840
985
  *
841
986
  * @template TOperation - String literal type representing operation names
842
- * @template TIntegrations - Record mapping operation names to their integrations
987
+ * @template TIntegrations - Record mapping integration keys to their integrations
843
988
  */
844
989
  export class RestApi<
845
990
  TOperation extends string,
846
- TIntegrations extends Record<TOperation, RestApiIntegration>,
991
+ TIntegrations extends ApiIntegrations<TOperation, RestApiIntegration>,
847
992
  > extends Construct {
848
993
  /** The underlying CDK RestApi instance */
849
994
  public readonly api: _RestApi;
850
995
 
851
- /** Map of operation names to their API Gateway integrations */
996
+ /** Map of integration keys to their API Gateway integrations */
852
997
  public readonly integrations: TIntegrations;
853
998
 
854
999
  constructor(
@@ -880,9 +1025,25 @@ export class RestApi<
880
1025
  (c) => c instanceof Stage,
881
1026
  );
882
1027
 
1028
+ // Resolve the integration for a given operation.
1029
+ // If the operation has a dedicated integration, use it; otherwise fall back to $router.
1030
+ const resolveIntegration = (op: TOperation): RestApiIntegration => {
1031
+ const record = integrations as Record<string, RestApiIntegration>;
1032
+ if (op in record) {
1033
+ return record[op];
1034
+ }
1035
+ if ('$router' in record) {
1036
+ return record['$router'];
1037
+ }
1038
+ throw new Error(
1039
+ \`No integration found for operation '\${op}' and no $router integration available\`,
1040
+ );
1041
+ };
1042
+
883
1043
  // Create API resources and methods for each operation
884
1044
  (Object.entries(operations) as [TOperation, OperationDetails][]).map(
885
1045
  ([op, details]) => {
1046
+ const integration = resolveIntegration(op);
886
1047
  const resource = this.getOrCreateResource(
887
1048
  this.api.root,
888
1049
  (details.path.startsWith('/')
@@ -892,8 +1053,8 @@ export class RestApi<
892
1053
  );
893
1054
  resource.addMethod(
894
1055
  details.method,
895
- integrations[op].integration,
896
- integrations[op].options,
1056
+ integration.integration,
1057
+ integration.options,
897
1058
  );
898
1059
  },
899
1060
  );
@@ -956,6 +1117,7 @@ import {
956
1117
  Grant,
957
1118
  } from 'aws-cdk-lib/aws-iam';
958
1119
  import {
1120
+ ApiIntegrations,
959
1121
  IntegrationBuilder,
960
1122
  RestApiIntegration,
961
1123
  } from '../../core/api/utils.js';
@@ -971,7 +1133,7 @@ import {
971
1133
  * @template TIntegrations - Map of operation names to their integrations
972
1134
  */
973
1135
  export interface TestApiProps<
974
- TIntegrations extends Record<Operations, RestApiIntegration>,
1136
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
975
1137
  > {
976
1138
  /**
977
1139
  * Map of operation names to their API Gateway integrations
@@ -985,7 +1147,7 @@ export interface TestApiProps<
985
1147
  * @template TIntegrations - Map of operation names to their integrations
986
1148
  */
987
1149
  export class TestApi<
988
- TIntegrations extends Record<Operations, RestApiIntegration>,
1150
+ TIntegrations extends ApiIntegrations<Operations, RestApiIntegration>,
989
1151
  > extends RestApi<Operations, TIntegrations> {
990
1152
  /**
991
1153
  * Creates default integrations for all operations, which implement each operation as
@@ -996,8 +1158,9 @@ export class TestApi<
996
1158
  */
997
1159
  public static defaultIntegrations = (scope: Construct) => {
998
1160
  return IntegrationBuilder.rest({
1161
+ pattern: 'isolated',
999
1162
  operations: OPERATION_DETAILS,
1000
- defaultIntegrationOptions: {
1163
+ defaultIntegrationOptions: <FunctionProps>{
1001
1164
  runtime: Runtime.PYTHON_3_12,
1002
1165
  handler: 'run.sh',
1003
1166
  code: Code.fromAsset(
@@ -1017,7 +1180,7 @@ export class TestApi<
1017
1180
  AWS_LWA_INVOKE_MODE: 'response_stream',
1018
1181
  AWS_LAMBDA_EXEC_WRAPPER: '/opt/bootstrap',
1019
1182
  },
1020
- } satisfies FunctionProps,
1183
+ },
1021
1184
  buildDefaultIntegration: (op, props: FunctionProps) => {
1022
1185
  const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
1023
1186
  const stack = Stack.of(scope);
@@ -1179,20 +1342,46 @@ export interface HttpApiIntegration {
1179
1342
  }
1180
1343
 
1181
1344
  /**
1182
- * Options for constructing an IntegrationBuilder
1345
+ * Common options shared by all IntegrationBuilder configurations
1183
1346
  */
1184
- export interface IntegrationBuilderProps<
1347
+ interface IntegrationBuilderPropsBase<
1185
1348
  TOperation extends string,
1186
- TBaseIntegration,
1187
1349
  TDefaultIntegrationProps extends object,
1188
- TDefaultIntegration extends TBaseIntegration,
1189
1350
  > {
1190
1351
  /** Map of operation names to their API path and HTTP method details */
1191
1352
  operations: Record<TOperation, OperationDetails>;
1192
1353
 
1193
1354
  /** Default configuration options for integrations */
1194
1355
  defaultIntegrationOptions: TDefaultIntegrationProps;
1356
+ }
1357
+
1358
+ /**
1359
+ * Options for constructing an IntegrationBuilder with a shared integration pattern.
1360
+ * A single default integration is built once (for '$router') and reused for all operations.
1361
+ */
1362
+ interface SharedIntegrationBuilderProps<
1363
+ TOperation extends string,
1364
+ TDefaultIntegrationProps extends object,
1365
+ TDefaultIntegration,
1366
+ > extends IntegrationBuilderPropsBase<TOperation, TDefaultIntegrationProps> {
1367
+ pattern: 'shared';
1368
+ /** Function to create a default integration for the shared router */
1369
+ buildDefaultIntegration: (
1370
+ op: '$router',
1371
+ props: TDefaultIntegrationProps,
1372
+ ) => TDefaultIntegration;
1373
+ }
1195
1374
 
1375
+ /**
1376
+ * Options for constructing an IntegrationBuilder with an isolated integration pattern.
1377
+ * A separate default integration is built per operation.
1378
+ */
1379
+ interface IsolatedIntegrationBuilderProps<
1380
+ TOperation extends string,
1381
+ TDefaultIntegrationProps extends object,
1382
+ TDefaultIntegration,
1383
+ > extends IntegrationBuilderPropsBase<TOperation, TDefaultIntegrationProps> {
1384
+ pattern: 'isolated';
1196
1385
  /** Function to create a default integration for an operation */
1197
1386
  buildDefaultIntegration: (
1198
1387
  op: TOperation,
@@ -1200,6 +1389,71 @@ export interface IntegrationBuilderProps<
1200
1389
  ) => TDefaultIntegration;
1201
1390
  }
1202
1391
 
1392
+ /**
1393
+ * Options for constructing an IntegrationBuilder
1394
+ */
1395
+ export type IntegrationBuilderProps<
1396
+ TOperation extends string,
1397
+ TBaseIntegration,
1398
+ TDefaultIntegrationProps extends object,
1399
+ TDefaultIntegration extends TBaseIntegration,
1400
+ > =
1401
+ | SharedIntegrationBuilderProps<
1402
+ TOperation,
1403
+ TDefaultIntegrationProps,
1404
+ TDefaultIntegration
1405
+ >
1406
+ | IsolatedIntegrationBuilderProps<
1407
+ TOperation,
1408
+ TDefaultIntegrationProps,
1409
+ TDefaultIntegration
1410
+ >;
1411
+
1412
+ /**
1413
+ * Extracts the IntegrationBuilderProps variant matching the given pattern.
1414
+ */
1415
+ type IntegrationBuilderPropsForPattern<
1416
+ TOperation extends string,
1417
+ TBaseIntegration,
1418
+ TDefaultIntegrationProps extends object,
1419
+ TDefaultIntegration extends TBaseIntegration,
1420
+ TPattern extends 'shared' | 'isolated',
1421
+ > = Extract<
1422
+ IntegrationBuilderProps<
1423
+ TOperation,
1424
+ TBaseIntegration,
1425
+ TDefaultIntegrationProps,
1426
+ TDefaultIntegration
1427
+ >,
1428
+ { pattern: TPattern }
1429
+ >;
1430
+
1431
+ /**
1432
+ * Resolves the default integration keys based on the pattern.
1433
+ * - 'shared': only '$router' is a default key
1434
+ * - 'isolated': all TOperation keys are default keys
1435
+ */
1436
+ type DefaultIntegrationKeys<
1437
+ TPattern extends 'shared' | 'isolated',
1438
+ TOperation extends string,
1439
+ > = TPattern extends 'shared' ? '$router' : TOperation;
1440
+
1441
+ /**
1442
+ * Shared integration record: $router is required, individual operations are optional.
1443
+ */
1444
+ type SharedIntegrations<TOperation extends string, TBaseIntegration> = {
1445
+ $router: TBaseIntegration;
1446
+ } & Partial<Record<TOperation, TBaseIntegration>>;
1447
+
1448
+ /**
1449
+ * Valid integration record types for an API.
1450
+ * Either all operations are provided (isolated), or $router is provided
1451
+ * with optional per-operation overrides (shared).
1452
+ */
1453
+ export type ApiIntegrations<TOperation extends string, TBaseIntegration> =
1454
+ | SharedIntegrations<TOperation, TBaseIntegration>
1455
+ | (Record<TOperation, TBaseIntegration> & { $router?: never });
1456
+
1203
1457
  /**
1204
1458
  * A builder class for creating API integrations with flexible configuration options.
1205
1459
  *
@@ -1208,16 +1462,18 @@ export interface IntegrationBuilderProps<
1208
1462
  *
1209
1463
  * @template TOperation - String literal type representing operation names
1210
1464
  * @template TBaseIntegration - Base type for all integrations
1211
- * @template TIntegrations - Record mapping operation names to their integrations
1465
+ * @template TIntegrations - Record mapping integration keys to their integrations
1212
1466
  * @template TDefaultIntegrationProps - Type for default integration properties
1213
1467
  * @template TDefaultIntegration - Type for default integration implementation
1468
+ * @template TPattern - The integration pattern ('shared' or 'isolated')
1214
1469
  */
1215
1470
  export class IntegrationBuilder<
1216
1471
  TOperation extends string,
1217
1472
  TBaseIntegration,
1218
- TIntegrations extends Record<TOperation, TBaseIntegration>,
1473
+ TIntegrations extends Record<string, TBaseIntegration>,
1219
1474
  TDefaultIntegrationProps extends object,
1220
1475
  TDefaultIntegration extends TBaseIntegration,
1476
+ TPattern extends 'shared' | 'isolated',
1221
1477
  > {
1222
1478
  /** Options for the integration builder */
1223
1479
  private options: IntegrationBuilderProps<
@@ -1235,23 +1491,25 @@ export class IntegrationBuilder<
1235
1491
  */
1236
1492
  public static http = <
1237
1493
  TOperation extends string,
1238
- TIntegrations extends Record<TOperation, TDefaultIntegration>,
1239
1494
  TDefaultIntegrationProps extends object,
1240
1495
  TDefaultIntegration extends HttpApiIntegration,
1496
+ TPattern extends 'shared' | 'isolated',
1241
1497
  >(
1242
- options: IntegrationBuilderProps<
1498
+ options: IntegrationBuilderPropsForPattern<
1243
1499
  TOperation,
1244
1500
  HttpApiIntegration,
1245
1501
  TDefaultIntegrationProps,
1246
- TDefaultIntegration
1502
+ TDefaultIntegration,
1503
+ TPattern
1247
1504
  >,
1248
1505
  ) => {
1249
1506
  return new IntegrationBuilder<
1250
1507
  TOperation,
1251
1508
  HttpApiIntegration,
1252
- TIntegrations,
1509
+ Record<DefaultIntegrationKeys<TPattern, TOperation>, TDefaultIntegration>,
1253
1510
  TDefaultIntegrationProps,
1254
- TDefaultIntegration
1511
+ TDefaultIntegration,
1512
+ TPattern
1255
1513
  >(options);
1256
1514
  };
1257
1515
 
@@ -1260,23 +1518,25 @@ export class IntegrationBuilder<
1260
1518
  */
1261
1519
  public static rest = <
1262
1520
  TOperation extends string,
1263
- TIntegrations extends Record<TOperation, TDefaultIntegration>,
1264
1521
  TDefaultIntegrationProps extends object,
1265
1522
  TDefaultIntegration extends RestApiIntegration,
1523
+ TPattern extends 'shared' | 'isolated',
1266
1524
  >(
1267
- options: IntegrationBuilderProps<
1525
+ options: IntegrationBuilderPropsForPattern<
1268
1526
  TOperation,
1269
1527
  RestApiIntegration,
1270
1528
  TDefaultIntegrationProps,
1271
- TDefaultIntegration
1529
+ TDefaultIntegration,
1530
+ TPattern
1272
1531
  >,
1273
1532
  ) => {
1274
1533
  return new IntegrationBuilder<
1275
1534
  TOperation,
1276
1535
  RestApiIntegration,
1277
- TIntegrations,
1536
+ Record<DefaultIntegrationKeys<TPattern, TOperation>, TDefaultIntegration>,
1278
1537
  TDefaultIntegrationProps,
1279
- TDefaultIntegration
1538
+ TDefaultIntegration,
1539
+ TPattern
1280
1540
  >(options);
1281
1541
  };
1282
1542
 
@@ -1301,13 +1561,20 @@ export class IntegrationBuilder<
1301
1561
  TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
1302
1562
  >(overrides: TOverrideIntegrations) {
1303
1563
  this.integrations = { ...this.integrations, ...overrides };
1304
- // Re-type to include the overridden integration types
1564
+ // Re-type to include the overridden integration types.
1565
+ // If all operations are overridden in a 'shared' pattern, drop '$router' from the type.
1566
+ type MergedIntegrations = [TOperation] extends [keyof TOverrideIntegrations]
1567
+ ? Omit<TIntegrations, keyof TOverrideIntegrations | '$router'> &
1568
+ TOverrideIntegrations
1569
+ : Omit<TIntegrations, keyof TOverrideIntegrations> &
1570
+ TOverrideIntegrations;
1305
1571
  return this as unknown as IntegrationBuilder<
1306
1572
  TOperation,
1307
1573
  TBaseIntegration,
1308
- Omit<TIntegrations, keyof TOverrideIntegrations> & TOverrideIntegrations,
1574
+ MergedIntegrations,
1309
1575
  TDefaultIntegrationProps,
1310
- TDefaultIntegration
1576
+ TDefaultIntegration,
1577
+ TPattern
1311
1578
  >;
1312
1579
  }
1313
1580
 
@@ -1333,24 +1600,39 @@ export class IntegrationBuilder<
1333
1600
  * 1. Including all custom overrides provided via withOverrides()
1334
1601
  * 2. Creating default integrations for any operations without custom overrides
1335
1602
  *
1336
- * @returns A complete map of operation names to their integrations
1603
+ * For the 'shared' pattern, a single router integration is built and assigned to '$router'.
1604
+ * For the 'isolated' pattern, each operation without an override gets its own integration.
1605
+ *
1606
+ * @returns A complete map of integration keys to their integrations
1337
1607
  */
1338
1608
  public build(): TIntegrations {
1609
+ const options = this.options;
1339
1610
  return {
1340
- ...this.integrations,
1341
1611
  ...Object.fromEntries(
1342
- (Object.keys(this.options.operations) as TOperation[])
1343
- .filter(
1344
- (op) => !this.integrations[op as keyof typeof this.integrations],
1345
- )
1346
- .map((op) => [
1347
- op,
1348
- this.options.buildDefaultIntegration(
1349
- op,
1350
- this.options.defaultIntegrationOptions,
1351
- ),
1352
- ]),
1612
+ options.pattern === 'shared'
1613
+ ? [
1614
+ [
1615
+ '$router',
1616
+ options.buildDefaultIntegration(
1617
+ '$router',
1618
+ options.defaultIntegrationOptions,
1619
+ ),
1620
+ ],
1621
+ ]
1622
+ : (Object.keys(options.operations) as TOperation[])
1623
+ .filter(
1624
+ (op) =>
1625
+ !this.integrations[op as keyof typeof this.integrations],
1626
+ )
1627
+ .map((op) => [
1628
+ op,
1629
+ options.buildDefaultIntegration(
1630
+ op,
1631
+ options.defaultIntegrationOptions,
1632
+ ),
1633
+ ]),
1353
1634
  ),
1635
+ ...this.integrations,
1354
1636
  } as unknown as TIntegrations;
1355
1637
  }
1356
1638
  }