@aws/nx-plugin 0.20.0 → 0.22.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 (79) hide show
  1. package/LICENSE-THIRD-PARTY +531 -21
  2. package/README.md +3 -9
  3. package/generators.json +14 -0
  4. package/package.json +7 -7
  5. package/src/cloudscape-website/cognito-auth/__snapshots__/generator.spec.ts.snap +1 -0
  6. package/src/cloudscape-website/cognito-auth/files/common/constructs/src/core/user-identity.ts.template +1 -0
  7. package/src/infra/app/__snapshots__/generator.spec.ts.snap +54 -12
  8. package/src/infra/app/generator.js +14 -0
  9. package/src/infra/app/generator.js.map +1 -1
  10. package/src/open-api/ts-metadata/__snapshots__/generator.spec.ts.snap +49 -0
  11. package/src/open-api/ts-metadata/files/metadata.gen.ts.template +17 -0
  12. package/src/open-api/ts-metadata/generator.d.ts +16 -0
  13. package/src/open-api/ts-metadata/generator.js +32 -0
  14. package/src/open-api/ts-metadata/generator.js.map +1 -0
  15. package/src/{utils/http-api.d.ts → open-api/ts-metadata/schema.d.ts} +5 -2
  16. package/src/open-api/ts-metadata/schema.json +20 -0
  17. package/src/open-api/utils/codegen-data.js +3 -0
  18. package/src/open-api/utils/codegen-data.js.map +1 -1
  19. package/src/open-api/utils/normalise.js +6 -0
  20. package/src/open-api/utils/normalise.js.map +1 -1
  21. package/src/preset/__snapshots__/generator.spec.ts.snap +199 -0
  22. package/src/preset/files/README.md +107 -0
  23. package/src/preset/generator.d.ts +10 -0
  24. package/src/preset/generator.js +63 -0
  25. package/src/preset/generator.js.map +1 -0
  26. package/src/preset/schema.d.ts +7 -0
  27. package/src/preset/schema.json +14 -0
  28. package/src/py/fast-api/__snapshots__/generator.spec.ts.snap +972 -0
  29. package/src/py/fast-api/files/app/__name__/init.py.template +8 -0
  30. package/src/py/fast-api/generator.js +53 -15
  31. package/src/py/fast-api/generator.js.map +1 -1
  32. package/src/py/fast-api/react/__snapshots__/generator.spec.ts.snap +2 -2
  33. package/src/py/fast-api/react/files/website/components/__apiNameClassName__Provider.tsx.template +1 -1
  34. package/src/py/fast-api/react/generator.js +6 -23
  35. package/src/py/fast-api/react/generator.js.map +1 -1
  36. package/src/py/fast-api/react/open-api.d.ts +14 -0
  37. package/src/py/fast-api/react/open-api.js +53 -0
  38. package/src/py/fast-api/react/open-api.js.map +1 -0
  39. package/src/py/fast-api/schema.d.ts +3 -0
  40. package/src/py/fast-api/schema.json +8 -0
  41. package/src/setup-tests.d.ts +2 -0
  42. package/src/setup-tests.js +14 -0
  43. package/src/setup-tests.js.map +1 -0
  44. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +1061 -88
  45. package/src/trpc/backend/files/backend/src/client/index.ts.template +2 -13
  46. package/src/trpc/backend/files/backend/src/index.ts.template +1 -0
  47. package/src/trpc/backend/files/backend/src/init.ts.template +4 -4
  48. package/src/trpc/backend/files/backend/src/middleware/index.ts.template +3 -2
  49. package/src/trpc/backend/files/backend/src/router.ts.template +11 -2
  50. package/src/trpc/backend/generator.js +12 -10
  51. package/src/trpc/backend/generator.js.map +1 -1
  52. package/src/trpc/backend/schema.d.ts +1 -1
  53. package/src/trpc/backend/schema.json +8 -0
  54. package/src/trpc/react/__snapshots__/generator.spec.ts.snap +4 -19
  55. package/src/trpc/react/files/src/components/TrpcClients/TrpcProvider.tsx.template +2 -13
  56. package/src/trpc/react/generator.js +1 -1
  57. package/src/trpc/react/generator.js.map +1 -1
  58. package/src/ts/lib/__snapshots__/generator.spec.ts.snap +26 -9
  59. package/src/ts/lib/generator.js +9 -0
  60. package/src/ts/lib/generator.js.map +1 -1
  61. package/src/ts/mcp-server/__snapshots__/generator.spec.ts.snap +1 -4
  62. package/src/ts/nx-generator/generator.js +4 -3
  63. package/src/ts/nx-generator/generator.js.map +1 -1
  64. package/src/utils/api-constructs/api-constructs.d.ts +29 -0
  65. package/src/utils/api-constructs/api-constructs.js +65 -0
  66. package/src/utils/api-constructs/api-constructs.js.map +1 -0
  67. package/src/utils/api-constructs/files/app/apis/http/__apiNameKebabCase__.ts.template +135 -0
  68. package/src/utils/api-constructs/files/app/apis/rest/__apiNameKebabCase__.ts.template +156 -0
  69. package/src/utils/api-constructs/files/core/api/http/http-api.ts.template +112 -0
  70. package/src/utils/api-constructs/files/core/api/rest/rest-api.ts.template +137 -0
  71. package/src/utils/api-constructs/files/core/api/trpc/trpc-utils.ts.template +67 -0
  72. package/src/utils/api-constructs/files/core/api/utils/utils.ts.template +223 -0
  73. package/src/utils/versions.d.ts +3 -3
  74. package/src/utils/versions.js +2 -2
  75. package/src/py/fast-api/files/common/constructs/src/app/http-apis/__apiNameKebabCase__.ts.template +0 -22
  76. package/src/trpc/backend/files/common/constructs/src/app/http-apis/__apiNameKebabCase__.ts.template +0 -22
  77. package/src/utils/files/http-api/common/constructs/src/core/http-api.ts.template +0 -87
  78. package/src/utils/http-api.js +0 -51
  79. package/src/utils/http-api.js.map +0 -1
@@ -1,14 +1,7 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
3
  exports[`trpc backend generator > should generate backend and schema projects > apps/test-api/backend/src/client/index.ts 1`] = `
4
- "import {
5
- createTRPCClient,
6
- httpBatchLink,
7
- httpLink,
8
- HTTPBatchLinkOptions,
9
- HTTPLinkOptions,
10
- splitLink,
11
- } from '@trpc/client';
4
+ "import { createTRPCClient, httpLink, HTTPLinkOptions } from '@trpc/client';
12
5
 
13
6
  import { AppRouter } from '../router.js';
14
7
  import { sigv4Fetch } from './sigv4.js';
@@ -18,20 +11,12 @@ export interface TestApiClientConfig {
18
11
  }
19
12
 
20
13
  export const createTestApiClient = (config: TestApiClientConfig) => {
21
- const linkOptions: HTTPLinkOptions<any> & HTTPBatchLinkOptions<any> = {
14
+ const linkOptions: HTTPLinkOptions<any> = {
22
15
  url: config.url,
23
16
  fetch: sigv4Fetch,
24
17
  };
25
18
  return createTRPCClient<AppRouter>({
26
- links: [
27
- splitLink({
28
- condition(op) {
29
- return op.context.skipBatch === true;
30
- },
31
- true: httpLink(linkOptions),
32
- false: httpBatchLink(linkOptions),
33
- }),
34
- ],
19
+ links: [httpLink(linkOptions)],
35
20
  });
36
21
  };
37
22
  "
@@ -52,6 +37,7 @@ export const sigv4Fetch = (async (...args) => {
52
37
 
53
38
  exports[`trpc backend generator > should generate backend and schema projects > apps/test-api/backend/src/index.ts 1`] = `
54
39
  "export type { AppRouter } from './router.js';
40
+ export { appRouter } from './router.js';
55
41
  export type { Context } from './init.js';
56
42
  export * from './client/index.js';
57
43
  "
@@ -75,10 +61,10 @@ export type Context = IMiddlewareContext;
75
61
  export const t = initTRPC.context<Context>().create();
76
62
 
77
63
  export const publicProcedure = t.procedure
78
- .unstable_concat(createLoggerPlugin())
79
- .unstable_concat(createTracerPlugin())
80
- .unstable_concat(createMetricsPlugin())
81
- .unstable_concat(createErrorPlugin());
64
+ .concat(createLoggerPlugin())
65
+ .concat(createTracerPlugin())
66
+ .concat(createMetricsPlugin())
67
+ .concat(createErrorPlugin());
82
68
  "
83
69
  `;
84
70
 
@@ -292,7 +278,7 @@ exports[`trpc backend generator > should generate backend and schema projects >
292
278
  } from '@trpc/server/adapters/aws-lambda';
293
279
  import { echo } from './procedures/echo.js';
294
280
  import { t } from './init.js';
295
- import { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
281
+ import type { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
296
282
 
297
283
  export const router = t.router;
298
284
 
@@ -333,50 +319,218 @@ export type IEchoOutput = z.TypeOf<typeof EchoOutputSchema>;
333
319
  "
334
320
  `;
335
321
 
336
- exports[`trpc backend generator > should set up shared constructs > http-api.ts 1`] = `
322
+ exports[`trpc backend generator > should set up shared constructs for http > http-api.ts 1`] = `
337
323
  "import { Construct } from 'constructs';
338
- import { CfnOutput, Duration } from 'aws-cdk-lib';
324
+ import { RuntimeConfig } from '../runtime-config.js';
325
+ import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam';
326
+ import { HttpApiIntegration, OperationDetails } from './utils.js';
327
+ import { CfnOutput } from 'aws-cdk-lib';
339
328
  import {
340
- CorsHttpMethod,
341
329
  HttpApi as _HttpApi,
330
+ HttpApiProps as _HttpApiProps,
342
331
  HttpMethod,
343
- IHttpRouteAuthorizer,
344
332
  } from 'aws-cdk-lib/aws-apigatewayv2';
345
- import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
346
- import { Code, Function, Runtime, Tracing } from 'aws-cdk-lib/aws-lambda';
347
- import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam';
348
- import { RuntimeConfig } from './runtime-config.js';
349
333
 
350
- export interface HttpApiProps {
334
+ /**
335
+ * Properties for creating an HttpApi construct.
336
+ *
337
+ * @template TIntegrations - Record mapping operation names to their integrations
338
+ * @template TOperation - String literal type representing operation names
339
+ */
340
+ export interface HttpApiProps<
341
+ TIntegrations extends Record<TOperation, HttpApiIntegration>,
342
+ TOperation extends string,
343
+ > extends _HttpApiProps {
344
+ /**
345
+ * Unique name for the API, used in runtime configuration
346
+ */
351
347
  readonly apiName: string;
352
- readonly handler: string;
353
- readonly handlerFilePath: string;
354
- readonly runtime: Runtime;
355
- readonly defaultAuthorizer: IHttpRouteAuthorizer;
356
- readonly allowedOrigins?: string[];
348
+ /**
349
+ * Map of operation names to their API path and HTTP method details
350
+ */
351
+ readonly operations: Record<TOperation, OperationDetails>;
352
+ /**
353
+ * Map of operation names to their API Gateway integrations
354
+ */
355
+ readonly integrations: TIntegrations;
357
356
  }
358
357
 
359
- export class HttpApi extends Construct {
358
+ /**
359
+ * A CDK construct that creates and configures an AWS API Gateway HTTP API.
360
+ *
361
+ * This class extends the base CDK HttpApi with additional functionality:
362
+ * - Type-safe operation and integration management
363
+ * - Automatic resource creation based on path patterns
364
+ * - Integration with runtime configuration for client discovery
365
+ *
366
+ * @template TOperation - String literal type representing operation names
367
+ * @template TIntegrations - Record mapping operation names to their integrations
368
+ */
369
+ export class HttpApi<
370
+ TOperation extends string,
371
+ TIntegrations extends Record<TOperation, HttpApiIntegration>,
372
+ > extends Construct {
373
+ /** The underlying CDK HttpApi instance */
360
374
  public readonly api: _HttpApi;
361
- public readonly routerFunction: Function;
362
375
 
363
- constructor(scope: Construct, id: string, props: HttpApiProps) {
376
+ /** Map of operation names to their API Gateway integrations */
377
+ public readonly integrations: TIntegrations;
378
+
379
+ constructor(
380
+ scope: Construct,
381
+ id: string,
382
+ {
383
+ apiName,
384
+ operations,
385
+ integrations,
386
+ ...props
387
+ }: HttpApiProps<TIntegrations, TOperation>,
388
+ ) {
364
389
  super(scope, id);
390
+ this.integrations = integrations;
365
391
 
366
- this.routerFunction = new Function(this, \`\${id}Handler\`, {
367
- timeout: Duration.seconds(30),
368
- runtime: props.runtime,
369
- handler: props.handler,
370
- code: Code.fromAsset(props.handlerFilePath),
371
- tracing: Tracing.ACTIVE,
372
- environment: {
373
- AWS_CONNECTION_REUSE_ENABLED: '1',
392
+ // Create the API Gateway REST API with logging enabled
393
+ this.api = new _HttpApi(this, 'Api', {
394
+ ...props,
395
+ });
396
+
397
+ // Create API resources and methods for each operation
398
+ (Object.entries(operations) as [TOperation, OperationDetails][]).map(
399
+ ([op, details]) => {
400
+ this.api.addRoutes({
401
+ path: details.path.startsWith('/')
402
+ ? details.path
403
+ : \`/\${details.path}\`,
404
+ methods: [details.method as HttpMethod],
405
+ integration: integrations[op].integration,
406
+ ...integrations[op].options,
407
+ });
408
+ },
409
+ );
410
+
411
+ new CfnOutput(this, \`\${apiName}Url\`, {
412
+ value: this.api.url!,
413
+ });
414
+
415
+ // Register the API URL in runtime configuration for client discovery
416
+ RuntimeConfig.ensure(this).config.apis = {
417
+ ...RuntimeConfig.ensure(this).config.apis!,
418
+ [apiName]: this.api.url!,
419
+ };
420
+ }
421
+
422
+ /**
423
+ * Grants IAM permissions to invoke any method on this API.
424
+ *
425
+ * @param grantee - The IAM principal to grant permissions to
426
+ */
427
+ public grantInvokeAccess(grantee: IGrantable) {
428
+ Grant.addToPrincipal({
429
+ grantee,
430
+ actions: ['execute-api:Invoke'],
431
+ resourceArns: [this.api.arnForExecuteApi('*', '/*', '*')],
432
+ });
433
+ }
434
+ }
435
+ "
436
+ `;
437
+
438
+ exports[`trpc backend generator > should set up shared constructs for http > test-api.ts 1`] = `
439
+ "import { Construct } from 'constructs';
440
+ import * as url from 'url';
441
+ import {
442
+ Code,
443
+ Runtime,
444
+ Function,
445
+ FunctionProps,
446
+ Tracing,
447
+ } from 'aws-cdk-lib/aws-lambda';
448
+ import { Duration } from 'aws-cdk-lib';
449
+ import { CorsHttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
450
+ import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
451
+ import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
452
+ import {
453
+ HttpApiIntegration,
454
+ IntegrationBuilder,
455
+ } from '../../core/api/utils.js';
456
+ import { HttpApi } from '../../core/api/http-api.js';
457
+ import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';
458
+ import { AppRouter, appRouter } from ':proj/test-api-backend';
459
+
460
+ // String union type for all API operation names
461
+ type Operations = Procedures<AppRouter>;
462
+
463
+ /**
464
+ * Properties for creating a TestApi construct
465
+ *
466
+ * @template TIntegrations - Map of operation names to their integrations
467
+ */
468
+ export interface TestApiProps<
469
+ TIntegrations extends Record<Operations, HttpApiIntegration>,
470
+ > {
471
+ /**
472
+ * Map of operation names to their API Gateway integrations
473
+ */
474
+ integrations: TIntegrations;
475
+ }
476
+
477
+ /**
478
+ * A CDK construct that creates and configures an AWS API Gateway HTTP API
479
+ * specifically for TestApi.
480
+ * @template TIntegrations - Map of operation names to their integrations
481
+ */
482
+ export class TestApi<
483
+ TIntegrations extends Record<Operations, HttpApiIntegration>,
484
+ > extends HttpApi<Operations, TIntegrations> {
485
+ /**
486
+ * Creates default integrations for all operations, which implement each operation as
487
+ * its own individual lambda function.
488
+ *
489
+ * @param scope - The CDK construct scope
490
+ * @returns An IntegrationBuilder with default lambda integrations
491
+ */
492
+ public static defaultIntegrations = (scope: Construct) => {
493
+ return IntegrationBuilder.http({
494
+ operations: routerToOperations(appRouter),
495
+ defaultIntegrationOptions: {
496
+ runtime: Runtime.NODEJS_LATEST,
497
+ handler: 'index.handler',
498
+ code: Code.fromAsset(
499
+ url.fileURLToPath(
500
+ new URL(
501
+ '../../../../../../dist/apps/test-api/backend/bundle',
502
+ import.meta.url,
503
+ ),
504
+ ),
505
+ ),
506
+ timeout: Duration.seconds(30),
507
+ tracing: Tracing.ACTIVE,
508
+ environment: {
509
+ AWS_CONNECTION_REUSE_ENABLED: '1',
510
+ },
511
+ } satisfies FunctionProps,
512
+ buildDefaultIntegration: (op, props: FunctionProps) => {
513
+ const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
514
+ return {
515
+ handler,
516
+ integration: new HttpLambdaIntegration(
517
+ \`TestApi\${op}Integration\`,
518
+ handler,
519
+ ),
520
+ };
374
521
  },
375
522
  });
523
+ };
376
524
 
377
- this.api = new _HttpApi(this, id, {
525
+ constructor(
526
+ scope: Construct,
527
+ id: string,
528
+ props: TestApiProps<TIntegrations>,
529
+ ) {
530
+ super(scope, id, {
531
+ apiName: 'TestApi',
378
532
  corsPreflight: {
379
- allowOrigins: props.allowedOrigins ?? ['*'],
533
+ allowOrigins: ['*'],
380
534
  allowMethods: [CorsHttpMethod.ANY],
381
535
  allowHeaders: [
382
536
  'authorization',
@@ -386,33 +540,450 @@ export class HttpApi extends Construct {
386
540
  'x-amz-security-token',
387
541
  ],
388
542
  },
389
- defaultAuthorizer: props.defaultAuthorizer,
543
+ defaultAuthorizer: new HttpIamAuthorizer(),
544
+ operations: routerToOperations(appRouter),
545
+ ...props,
390
546
  });
547
+ }
548
+ }
549
+ "
550
+ `;
391
551
 
392
- this.api.addRoutes({
393
- path: '/{proxy+}',
394
- methods: [
395
- HttpMethod.GET,
396
- HttpMethod.DELETE,
397
- HttpMethod.POST,
398
- HttpMethod.PUT,
399
- HttpMethod.PATCH,
400
- HttpMethod.HEAD,
401
- ],
402
- integration: new HttpLambdaIntegration(
403
- 'RouterIntegration',
404
- this.routerFunction,
552
+ exports[`trpc backend generator > should set up shared constructs for http > trpc-utils.ts 1`] = `
553
+ "import { TRPCRouterRecord, AnyTRPCRouter } from '@trpc/server';
554
+ import { OperationDetails } from './utils.js';
555
+ import { HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
556
+
557
+ /**
558
+ * Helper type that recursively extracts procedure names from a tRPC router.
559
+ * This type traverses the router structure and builds fully qualified procedure names
560
+ * with dot notation for nested routers.
561
+ *
562
+ * @template T - The tRPC router record type
563
+ * @template Prefix - The current path prefix for nested procedures
564
+ */
565
+ type _Procedures<T extends TRPCRouterRecord, Prefix extends string = ''> = {
566
+ [K in keyof T]: K extends string
567
+ ? T[K] extends TRPCRouterRecord
568
+ ? _Procedures<T[K], \`\${Prefix}\${K}.\`>
569
+ : \`\${Prefix}\${K}\`
570
+ : never;
571
+ }[keyof T];
572
+
573
+ /**
574
+ * Extracts all procedure names from a tRPC router as a union of string literals.
575
+ * This type is used to provide type-safe access to procedure names throughout the API.
576
+ *
577
+ * @template TRouter - The tRPC router type
578
+ */
579
+ export type Procedures<TRouter extends AnyTRPCRouter> = _Procedures<
580
+ TRouter['_def']['record']
581
+ >;
582
+
583
+ /**
584
+ * Converts a tRPC router to a map of API operations.
585
+ * This method recursively traverses the router structure and creates operation details
586
+ * for each procedure, mapping queries to GET methods and mutations to POST methods.
587
+ *
588
+ * @param router - The tRPC router to convert
589
+ * @param prefix - The current path prefix for nested procedures
590
+ * @returns A map of procedure names to their API operation details
591
+ */
592
+ export const routerToOperations = <TRouter extends AnyTRPCRouter>(
593
+ router: TRouter,
594
+ prefix = '',
595
+ ): Record<Procedures<TRouter>, OperationDetails> => {
596
+ return Object.fromEntries(
597
+ Object.entries(router._def.procedures).flatMap(
598
+ ([op, procedureOrRouter]: [string, any]) => {
599
+ const fullPath = prefix ? \`\${prefix}.\${op}\` : op;
600
+ return procedureOrRouter._def?.router
601
+ ? Object.entries(
602
+ routerToOperations<TRouter>(procedureOrRouter, fullPath),
603
+ )
604
+ : [
605
+ [
606
+ fullPath,
607
+ {
608
+ path: fullPath,
609
+ method:
610
+ procedureOrRouter._def.type === 'query'
611
+ ? HttpMethod.GET
612
+ : HttpMethod.POST,
613
+ },
614
+ ],
615
+ ];
616
+ },
617
+ ),
618
+ ) as Record<Procedures<TRouter>, OperationDetails>;
619
+ };
620
+ "
621
+ `;
622
+
623
+ exports[`trpc backend generator > should set up shared constructs for http > utils.ts 1`] = `
624
+ "import { Integration, MethodOptions } from 'aws-cdk-lib/aws-apigateway';
625
+ import {
626
+ HttpRouteIntegration,
627
+ AddRoutesOptions,
628
+ } from 'aws-cdk-lib/aws-apigatewayv2';
629
+
630
+ /**
631
+ * Type representing applicable HTTP Methods in API Gateway
632
+ */
633
+ export type HttpMethod =
634
+ | 'ANY'
635
+ | 'DELETE'
636
+ | 'GET'
637
+ | 'HEAD'
638
+ | 'OPTIONS'
639
+ | 'PATCH'
640
+ | 'POST'
641
+ | 'PUT';
642
+
643
+ /**
644
+ * Defines the details of an API operation.
645
+ */
646
+ export interface OperationDetails {
647
+ /**
648
+ * The URL path for the operation
649
+ */
650
+ path: string;
651
+
652
+ /**
653
+ * The HTTP method for the operation
654
+ */
655
+ method: HttpMethod;
656
+ }
657
+
658
+ /**
659
+ * Represents an API Gateway REST API integration that can be attached to API methods.
660
+ */
661
+ export interface RestApiIntegration {
662
+ integration: Integration;
663
+ options?: MethodOptions;
664
+ }
665
+
666
+ /**
667
+ * Represents an API Gateway HTTP API that can be attached to API methods.
668
+ */
669
+ export interface HttpApiIntegration {
670
+ integration: HttpRouteIntegration;
671
+ options?: Omit<AddRoutesOptions, 'path' | 'methods' | 'integration'>;
672
+ }
673
+
674
+ /**
675
+ * Options for constructing an IntegrationBuilder
676
+ */
677
+ export interface IntegrationBuilderProps<
678
+ TOperation extends string,
679
+ TBaseIntegration,
680
+ TDefaultIntegrationProps extends object,
681
+ TDefaultIntegration extends TBaseIntegration,
682
+ > {
683
+ /** Map of operation names to their API path and HTTP method details */
684
+ operations: Record<TOperation, OperationDetails>;
685
+
686
+ /** Default configuration options for integrations */
687
+ defaultIntegrationOptions: TDefaultIntegrationProps;
688
+
689
+ /** Function to create a default integration for an operation */
690
+ buildDefaultIntegration: (
691
+ op: TOperation,
692
+ props: TDefaultIntegrationProps,
693
+ ) => TDefaultIntegration;
694
+ }
695
+
696
+ /**
697
+ * A builder class for creating API integrations with flexible configuration options.
698
+ *
699
+ * This class implements the builder pattern to create a set of API integrations
700
+ * with support for default configurations and selective overrides.
701
+ *
702
+ * @template TOperation - String literal type representing operation names
703
+ * @template TBaseIntegration - Base type for all integrations
704
+ * @template TIntegrations - Record mapping operation names to their integrations
705
+ * @template TDefaultIntegrationProps - Type for default integration properties
706
+ * @template TDefaultIntegration - Type for default integration implementation
707
+ */
708
+ export class IntegrationBuilder<
709
+ TOperation extends string,
710
+ TBaseIntegration,
711
+ TIntegrations extends Record<TOperation, TBaseIntegration>,
712
+ TDefaultIntegrationProps extends object,
713
+ TDefaultIntegration extends TBaseIntegration,
714
+ > {
715
+ /** Options for the integration builder */
716
+ private options: IntegrationBuilderProps<
717
+ TOperation,
718
+ TBaseIntegration,
719
+ TDefaultIntegrationProps,
720
+ TDefaultIntegration
721
+ >;
722
+
723
+ /** Map of operation names to their custom integrations */
724
+ private integrations: Partial<TIntegrations> = {};
725
+
726
+ /**
727
+ * Create an Integration Builder for an HTTP API
728
+ */
729
+ public static http = <
730
+ TOperation extends string,
731
+ TIntegrations extends Record<TOperation, TDefaultIntegration>,
732
+ TDefaultIntegrationProps extends object,
733
+ TDefaultIntegration extends HttpApiIntegration,
734
+ >(
735
+ options: IntegrationBuilderProps<
736
+ TOperation,
737
+ HttpApiIntegration,
738
+ TDefaultIntegrationProps,
739
+ TDefaultIntegration
740
+ >,
741
+ ) => {
742
+ return new IntegrationBuilder<
743
+ TOperation,
744
+ HttpApiIntegration,
745
+ TIntegrations,
746
+ TDefaultIntegrationProps,
747
+ TDefaultIntegration
748
+ >(options);
749
+ };
750
+
751
+ /**
752
+ * Create an Integration Builder for a REST API
753
+ */
754
+ public static rest = <
755
+ TOperation extends string,
756
+ TIntegrations extends Record<TOperation, TDefaultIntegration>,
757
+ TDefaultIntegrationProps extends object,
758
+ TDefaultIntegration extends RestApiIntegration,
759
+ >(
760
+ options: IntegrationBuilderProps<
761
+ TOperation,
762
+ RestApiIntegration,
763
+ TDefaultIntegrationProps,
764
+ TDefaultIntegration
765
+ >,
766
+ ) => {
767
+ return new IntegrationBuilder<
768
+ TOperation,
769
+ RestApiIntegration,
770
+ TIntegrations,
771
+ TDefaultIntegrationProps,
772
+ TDefaultIntegration
773
+ >(options);
774
+ };
775
+
776
+ private constructor(
777
+ options: IntegrationBuilderProps<
778
+ TOperation,
779
+ TBaseIntegration,
780
+ TDefaultIntegrationProps,
781
+ TDefaultIntegration
782
+ >,
783
+ ) {
784
+ this.options = options;
785
+ }
786
+
787
+ /**
788
+ * Overrides default integrations with custom implementations for specific operations.
789
+ *
790
+ * @param overrides - Map of operation names to their custom integration implementations
791
+ * @returns The builder instance with updated type information reflecting the overrides
792
+ */
793
+ public withOverrides<
794
+ TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
795
+ >(overrides: TOverrideIntegrations) {
796
+ this.integrations = { ...this.integrations, ...overrides };
797
+ // Re-type to include the overridden integration types
798
+ return this as unknown as IntegrationBuilder<
799
+ TOperation,
800
+ TBaseIntegration,
801
+ Omit<TIntegrations, keyof TOverrideIntegrations> & TOverrideIntegrations,
802
+ TDefaultIntegrationProps,
803
+ TDefaultIntegration
804
+ >;
805
+ }
806
+
807
+ /**
808
+ * Updates the default integration options that will be used for operations
809
+ * without custom overrides.
810
+ *
811
+ * @param options - Partial default integration options to merge with existing defaults
812
+ * @returns The builder instance
813
+ */
814
+ public withDefaultOptions(options: Partial<TDefaultIntegrationProps>) {
815
+ this.options.defaultIntegrationOptions = {
816
+ ...this.options.defaultIntegrationOptions,
817
+ ...options,
818
+ };
819
+ return this;
820
+ }
821
+
822
+ /**
823
+ * Builds and returns the complete set of integrations.
824
+ *
825
+ * This method creates the final integration map by:
826
+ * 1. Including all custom overrides provided via withOverrides()
827
+ * 2. Creating default integrations for any operations without custom overrides
828
+ *
829
+ * @returns A complete map of operation names to their integrations
830
+ */
831
+ public build(): TIntegrations {
832
+ return {
833
+ ...this.integrations,
834
+ ...Object.fromEntries(
835
+ (Object.keys(this.options.operations) as TOperation[])
836
+ .filter(
837
+ (op) => !this.integrations[op as keyof typeof this.integrations],
838
+ )
839
+ .map((op) => [
840
+ op,
841
+ this.options.buildDefaultIntegration(
842
+ op,
843
+ this.options.defaultIntegrationOptions,
844
+ ),
845
+ ]),
405
846
  ),
847
+ } as unknown as TIntegrations;
848
+ }
849
+ }
850
+ "
851
+ `;
852
+
853
+ exports[`trpc backend generator > should set up shared constructs for rest > rest-api.ts 1`] = `
854
+ "import { Construct } from 'constructs';
855
+ import {
856
+ RestApi as _RestApi,
857
+ RestApiProps as _RestApiProps,
858
+ AccessLogFormat,
859
+ IResource,
860
+ LogGroupLogDestination,
861
+ MethodLoggingLevel,
862
+ } from 'aws-cdk-lib/aws-apigateway';
863
+ import { LogGroup } from 'aws-cdk-lib/aws-logs';
864
+ import { RuntimeConfig } from '../runtime-config.js';
865
+ import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam';
866
+ import { OperationDetails, RestApiIntegration } from './utils.js';
867
+
868
+ /**
869
+ * Properties for creating a RestApi construct.
870
+ *
871
+ * @template TIntegrations - Record mapping operation names to their integrations
872
+ * @template TOperation - String literal type representing operation names
873
+ */
874
+ export interface RestApiProps<
875
+ TIntegrations extends Record<TOperation, RestApiIntegration>,
876
+ TOperation extends string,
877
+ > extends _RestApiProps {
878
+ /**
879
+ * Unique name for the API, used in runtime configuration
880
+ */
881
+ readonly apiName: string;
882
+ /**
883
+ * Map of operation names to their API path and HTTP method details
884
+ */
885
+ readonly operations: Record<TOperation, OperationDetails>;
886
+ /**
887
+ * Map of operation names to their API Gateway integrations
888
+ */
889
+ readonly integrations: TIntegrations;
890
+ }
891
+
892
+ /**
893
+ * A CDK construct that creates and configures an AWS API Gateway REST API.
894
+ *
895
+ * This class extends the base CDK RestApi with additional functionality:
896
+ * - Type-safe operation and integration management
897
+ * - Automatic resource creation based on path patterns
898
+ * - Integration with runtime configuration for client discovery
899
+ *
900
+ * @template TOperation - String literal type representing operation names
901
+ * @template TIntegrations - Record mapping operation names to their integrations
902
+ */
903
+ export class RestApi<
904
+ TOperation extends string,
905
+ TIntegrations extends Record<TOperation, RestApiIntegration>,
906
+ > extends Construct {
907
+ /** The underlying CDK RestApi instance */
908
+ public readonly api: _RestApi;
909
+
910
+ /** Map of operation names to their API Gateway integrations */
911
+ public readonly integrations: TIntegrations;
912
+
913
+ constructor(
914
+ scope: Construct,
915
+ id: string,
916
+ {
917
+ apiName,
918
+ operations,
919
+ integrations,
920
+ ...props
921
+ }: RestApiProps<TIntegrations, TOperation>,
922
+ ) {
923
+ super(scope, id);
924
+ this.integrations = integrations;
925
+
926
+ // Create the API Gateway REST API with logging enabled
927
+ this.api = new _RestApi(this, 'Api', {
928
+ deployOptions: {
929
+ accessLogDestination: new LogGroupLogDestination(
930
+ new LogGroup(this, 'AccessLogs'),
931
+ ),
932
+ accessLogFormat: AccessLogFormat.clf(),
933
+ loggingLevel: MethodLoggingLevel.INFO,
934
+ },
935
+ ...props,
406
936
  });
407
937
 
408
- new CfnOutput(this, \`\${props.apiName}Url\`, { value: this.api.url! });
938
+ // Create API resources and methods for each operation
939
+ (Object.entries(operations) as [TOperation, OperationDetails][]).map(
940
+ ([op, details]) => {
941
+ const resource = this.getOrCreateResource(
942
+ this.api.root,
943
+ (details.path.startsWith('/')
944
+ ? details.path.slice(1)
945
+ : details.path
946
+ ).split('/'),
947
+ );
948
+ resource.addMethod(
949
+ details.method,
950
+ integrations[op].integration,
951
+ integrations[op].options,
952
+ );
953
+ },
954
+ );
409
955
 
410
- RuntimeConfig.ensure(this).config.httpApis = {
411
- ...RuntimeConfig.ensure(this).config.httpApis!,
412
- [props.apiName]: this.api.url!,
956
+ // Register the API URL in runtime configuration for client discovery
957
+ RuntimeConfig.ensure(this).config.apis = {
958
+ ...RuntimeConfig.ensure(this).config.apis!,
959
+ [apiName]: this.api.url!,
413
960
  };
414
961
  }
415
962
 
963
+ /**
964
+ * Recursively creates or retrieves API Gateway resources based on a path pattern.
965
+ *
966
+ * @param resource - The parent API Gateway resource
967
+ * @param pathParts - Array of path segments to create or retrieve
968
+ * @returns The API Gateway resource at the end of the path
969
+ */
970
+ private getOrCreateResource(
971
+ resource: IResource,
972
+ [nextPathPart, ...pathParts]: string[],
973
+ ): IResource {
974
+ if (!nextPathPart) {
975
+ return resource;
976
+ }
977
+ const childResource =
978
+ resource.getResource(nextPathPart) ?? resource.addResource(nextPathPart);
979
+ return this.getOrCreateResource(childResource, pathParts);
980
+ }
981
+
982
+ /**
983
+ * Grants IAM permissions to invoke any method on this API.
984
+ *
985
+ * @param grantee - The IAM principal to grant permissions to
986
+ */
416
987
  public grantInvokeAccess(grantee: IGrantable) {
417
988
  Grant.addToPrincipal({
418
989
  grantee,
@@ -424,33 +995,435 @@ export class HttpApi extends Construct {
424
995
  "
425
996
  `;
426
997
 
427
- exports[`trpc backend generator > should set up shared constructs > index.ts 1`] = `
428
- "export * from './test-api.js';
429
- "
430
- `;
431
-
432
- exports[`trpc backend generator > should set up shared constructs > test-api.ts 1`] = `
998
+ exports[`trpc backend generator > should set up shared constructs for rest > test-api.ts 1`] = `
433
999
  "import { Construct } from 'constructs';
434
1000
  import * as url from 'url';
435
- import { HttpApi } from '../../core/http-api.js';
436
- import { HttpIamAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
437
- import { Runtime } from 'aws-cdk-lib/aws-lambda';
1001
+ import {
1002
+ Code,
1003
+ Runtime,
1004
+ Function,
1005
+ FunctionProps,
1006
+ Tracing,
1007
+ } from 'aws-cdk-lib/aws-lambda';
1008
+ import {
1009
+ AuthorizationType,
1010
+ Cors,
1011
+ LambdaIntegration,
1012
+ } from 'aws-cdk-lib/aws-apigateway';
1013
+ import { Duration, Stack } from 'aws-cdk-lib';
1014
+ import {
1015
+ PolicyDocument,
1016
+ PolicyStatement,
1017
+ Effect,
1018
+ AccountPrincipal,
1019
+ AnyPrincipal,
1020
+ } from 'aws-cdk-lib/aws-iam';
1021
+ import {
1022
+ IntegrationBuilder,
1023
+ RestApiIntegration,
1024
+ } from '../../core/api/utils.js';
1025
+ import { RestApi } from '../../core/api/rest-api.js';
1026
+ import { Procedures, routerToOperations } from '../../core/api/trpc-utils.js';
1027
+ import { AppRouter, appRouter } from ':proj/test-api-backend';
1028
+
1029
+ // String union type for all API operation names
1030
+ type Operations = Procedures<AppRouter>;
1031
+
1032
+ /**
1033
+ * Properties for creating a TestApi construct
1034
+ *
1035
+ * @template TIntegrations - Map of operation names to their integrations
1036
+ */
1037
+ export interface TestApiProps<
1038
+ TIntegrations extends Record<Operations, RestApiIntegration>,
1039
+ > {
1040
+ /**
1041
+ * Map of operation names to their API Gateway integrations
1042
+ */
1043
+ integrations: TIntegrations;
1044
+ }
1045
+
1046
+ /**
1047
+ * A CDK construct that creates and configures an AWS API Gateway REST API
1048
+ * specifically for TestApi.
1049
+ * @template TIntegrations - Map of operation names to their integrations
1050
+ */
1051
+ export class TestApi<
1052
+ TIntegrations extends Record<Operations, RestApiIntegration>,
1053
+ > extends RestApi<Operations, TIntegrations> {
1054
+ /**
1055
+ * Creates default integrations for all operations, which implement each operation as
1056
+ * its own individual lambda function.
1057
+ *
1058
+ * @param scope - The CDK construct scope
1059
+ * @returns An IntegrationBuilder with default lambda integrations
1060
+ */
1061
+ public static defaultIntegrations = (scope: Construct) => {
1062
+ return IntegrationBuilder.rest({
1063
+ operations: routerToOperations(appRouter),
1064
+ defaultIntegrationOptions: {
1065
+ runtime: Runtime.NODEJS_LATEST,
1066
+ handler: 'index.handler',
1067
+ code: Code.fromAsset(
1068
+ url.fileURLToPath(
1069
+ new URL(
1070
+ '../../../../../../dist/apps/test-api/backend/bundle',
1071
+ import.meta.url,
1072
+ ),
1073
+ ),
1074
+ ),
1075
+ timeout: Duration.seconds(30),
1076
+ tracing: Tracing.ACTIVE,
1077
+ environment: {
1078
+ AWS_CONNECTION_REUSE_ENABLED: '1',
1079
+ },
1080
+ } satisfies FunctionProps,
1081
+ buildDefaultIntegration: (op, props: FunctionProps) => {
1082
+ const handler = new Function(scope, \`TestApi\${op}Handler\`, props);
1083
+ return { handler, integration: new LambdaIntegration(handler) };
1084
+ },
1085
+ });
1086
+ };
438
1087
 
439
- export class TestApi extends HttpApi {
440
- constructor(scope: Construct, id: string) {
1088
+ constructor(
1089
+ scope: Construct,
1090
+ id: string,
1091
+ props: TestApiProps<TIntegrations>,
1092
+ ) {
441
1093
  super(scope, id, {
442
- defaultAuthorizer: new HttpIamAuthorizer(),
443
1094
  apiName: 'TestApi',
444
- runtime: Runtime.NODEJS_LATEST,
445
- handler: 'index.handler',
446
- handlerFilePath: url.fileURLToPath(
447
- new URL(
448
- '../../../../../../dist/apps/test-api/backend/bundle',
449
- import.meta.url,
450
- ),
451
- ),
1095
+ defaultMethodOptions: {
1096
+ authorizationType: AuthorizationType.IAM,
1097
+ },
1098
+ defaultCorsPreflightOptions: {
1099
+ allowOrigins: Cors.ALL_ORIGINS,
1100
+ allowMethods: Cors.ALL_METHODS,
1101
+ },
1102
+ policy: new PolicyDocument({
1103
+ statements: [
1104
+ // Here we grant any AWS credentials from the account that the project is deployed in to call the api.
1105
+ // Machine to machine fine-grained access can be defined here using more specific principals (eg roles or
1106
+ // users) and resources (eg which api paths may be invoked by which principal) if required.
1107
+ new PolicyStatement({
1108
+ effect: Effect.ALLOW,
1109
+ principals: [new AccountPrincipal(Stack.of(scope).account)],
1110
+ actions: ['execute-api:Invoke'],
1111
+ resources: ['execute-api:/*'],
1112
+ }),
1113
+ // Open up OPTIONS to allow browsers to make unauthenticated preflight requests
1114
+ new PolicyStatement({
1115
+ effect: Effect.ALLOW,
1116
+ principals: [new AnyPrincipal()],
1117
+ actions: ['execute-api:Invoke'],
1118
+ resources: ['execute-api:/*/OPTIONS/*'],
1119
+ }),
1120
+ ],
1121
+ }),
1122
+ operations: routerToOperations(appRouter),
1123
+ ...props,
452
1124
  });
453
1125
  }
454
1126
  }
455
1127
  "
456
1128
  `;
1129
+
1130
+ exports[`trpc backend generator > should set up shared constructs for rest > trpc-utils.ts 1`] = `
1131
+ "import { TRPCRouterRecord, AnyTRPCRouter } from '@trpc/server';
1132
+ import { OperationDetails } from './utils.js';
1133
+ import { HttpMethod } from 'aws-cdk-lib/aws-apigatewayv2';
1134
+
1135
+ /**
1136
+ * Helper type that recursively extracts procedure names from a tRPC router.
1137
+ * This type traverses the router structure and builds fully qualified procedure names
1138
+ * with dot notation for nested routers.
1139
+ *
1140
+ * @template T - The tRPC router record type
1141
+ * @template Prefix - The current path prefix for nested procedures
1142
+ */
1143
+ type _Procedures<T extends TRPCRouterRecord, Prefix extends string = ''> = {
1144
+ [K in keyof T]: K extends string
1145
+ ? T[K] extends TRPCRouterRecord
1146
+ ? _Procedures<T[K], \`\${Prefix}\${K}.\`>
1147
+ : \`\${Prefix}\${K}\`
1148
+ : never;
1149
+ }[keyof T];
1150
+
1151
+ /**
1152
+ * Extracts all procedure names from a tRPC router as a union of string literals.
1153
+ * This type is used to provide type-safe access to procedure names throughout the API.
1154
+ *
1155
+ * @template TRouter - The tRPC router type
1156
+ */
1157
+ export type Procedures<TRouter extends AnyTRPCRouter> = _Procedures<
1158
+ TRouter['_def']['record']
1159
+ >;
1160
+
1161
+ /**
1162
+ * Converts a tRPC router to a map of API operations.
1163
+ * This method recursively traverses the router structure and creates operation details
1164
+ * for each procedure, mapping queries to GET methods and mutations to POST methods.
1165
+ *
1166
+ * @param router - The tRPC router to convert
1167
+ * @param prefix - The current path prefix for nested procedures
1168
+ * @returns A map of procedure names to their API operation details
1169
+ */
1170
+ export const routerToOperations = <TRouter extends AnyTRPCRouter>(
1171
+ router: TRouter,
1172
+ prefix = '',
1173
+ ): Record<Procedures<TRouter>, OperationDetails> => {
1174
+ return Object.fromEntries(
1175
+ Object.entries(router._def.procedures).flatMap(
1176
+ ([op, procedureOrRouter]: [string, any]) => {
1177
+ const fullPath = prefix ? \`\${prefix}.\${op}\` : op;
1178
+ return procedureOrRouter._def?.router
1179
+ ? Object.entries(
1180
+ routerToOperations<TRouter>(procedureOrRouter, fullPath),
1181
+ )
1182
+ : [
1183
+ [
1184
+ fullPath,
1185
+ {
1186
+ path: fullPath,
1187
+ method:
1188
+ procedureOrRouter._def.type === 'query'
1189
+ ? HttpMethod.GET
1190
+ : HttpMethod.POST,
1191
+ },
1192
+ ],
1193
+ ];
1194
+ },
1195
+ ),
1196
+ ) as Record<Procedures<TRouter>, OperationDetails>;
1197
+ };
1198
+ "
1199
+ `;
1200
+
1201
+ exports[`trpc backend generator > should set up shared constructs for rest > utils.ts 1`] = `
1202
+ "import { Integration, MethodOptions } from 'aws-cdk-lib/aws-apigateway';
1203
+ import {
1204
+ HttpRouteIntegration,
1205
+ AddRoutesOptions,
1206
+ } from 'aws-cdk-lib/aws-apigatewayv2';
1207
+
1208
+ /**
1209
+ * Type representing applicable HTTP Methods in API Gateway
1210
+ */
1211
+ export type HttpMethod =
1212
+ | 'ANY'
1213
+ | 'DELETE'
1214
+ | 'GET'
1215
+ | 'HEAD'
1216
+ | 'OPTIONS'
1217
+ | 'PATCH'
1218
+ | 'POST'
1219
+ | 'PUT';
1220
+
1221
+ /**
1222
+ * Defines the details of an API operation.
1223
+ */
1224
+ export interface OperationDetails {
1225
+ /**
1226
+ * The URL path for the operation
1227
+ */
1228
+ path: string;
1229
+
1230
+ /**
1231
+ * The HTTP method for the operation
1232
+ */
1233
+ method: HttpMethod;
1234
+ }
1235
+
1236
+ /**
1237
+ * Represents an API Gateway REST API integration that can be attached to API methods.
1238
+ */
1239
+ export interface RestApiIntegration {
1240
+ integration: Integration;
1241
+ options?: MethodOptions;
1242
+ }
1243
+
1244
+ /**
1245
+ * Represents an API Gateway HTTP API that can be attached to API methods.
1246
+ */
1247
+ export interface HttpApiIntegration {
1248
+ integration: HttpRouteIntegration;
1249
+ options?: Omit<AddRoutesOptions, 'path' | 'methods' | 'integration'>;
1250
+ }
1251
+
1252
+ /**
1253
+ * Options for constructing an IntegrationBuilder
1254
+ */
1255
+ export interface IntegrationBuilderProps<
1256
+ TOperation extends string,
1257
+ TBaseIntegration,
1258
+ TDefaultIntegrationProps extends object,
1259
+ TDefaultIntegration extends TBaseIntegration,
1260
+ > {
1261
+ /** Map of operation names to their API path and HTTP method details */
1262
+ operations: Record<TOperation, OperationDetails>;
1263
+
1264
+ /** Default configuration options for integrations */
1265
+ defaultIntegrationOptions: TDefaultIntegrationProps;
1266
+
1267
+ /** Function to create a default integration for an operation */
1268
+ buildDefaultIntegration: (
1269
+ op: TOperation,
1270
+ props: TDefaultIntegrationProps,
1271
+ ) => TDefaultIntegration;
1272
+ }
1273
+
1274
+ /**
1275
+ * A builder class for creating API integrations with flexible configuration options.
1276
+ *
1277
+ * This class implements the builder pattern to create a set of API integrations
1278
+ * with support for default configurations and selective overrides.
1279
+ *
1280
+ * @template TOperation - String literal type representing operation names
1281
+ * @template TBaseIntegration - Base type for all integrations
1282
+ * @template TIntegrations - Record mapping operation names to their integrations
1283
+ * @template TDefaultIntegrationProps - Type for default integration properties
1284
+ * @template TDefaultIntegration - Type for default integration implementation
1285
+ */
1286
+ export class IntegrationBuilder<
1287
+ TOperation extends string,
1288
+ TBaseIntegration,
1289
+ TIntegrations extends Record<TOperation, TBaseIntegration>,
1290
+ TDefaultIntegrationProps extends object,
1291
+ TDefaultIntegration extends TBaseIntegration,
1292
+ > {
1293
+ /** Options for the integration builder */
1294
+ private options: IntegrationBuilderProps<
1295
+ TOperation,
1296
+ TBaseIntegration,
1297
+ TDefaultIntegrationProps,
1298
+ TDefaultIntegration
1299
+ >;
1300
+
1301
+ /** Map of operation names to their custom integrations */
1302
+ private integrations: Partial<TIntegrations> = {};
1303
+
1304
+ /**
1305
+ * Create an Integration Builder for an HTTP API
1306
+ */
1307
+ public static http = <
1308
+ TOperation extends string,
1309
+ TIntegrations extends Record<TOperation, TDefaultIntegration>,
1310
+ TDefaultIntegrationProps extends object,
1311
+ TDefaultIntegration extends HttpApiIntegration,
1312
+ >(
1313
+ options: IntegrationBuilderProps<
1314
+ TOperation,
1315
+ HttpApiIntegration,
1316
+ TDefaultIntegrationProps,
1317
+ TDefaultIntegration
1318
+ >,
1319
+ ) => {
1320
+ return new IntegrationBuilder<
1321
+ TOperation,
1322
+ HttpApiIntegration,
1323
+ TIntegrations,
1324
+ TDefaultIntegrationProps,
1325
+ TDefaultIntegration
1326
+ >(options);
1327
+ };
1328
+
1329
+ /**
1330
+ * Create an Integration Builder for a REST API
1331
+ */
1332
+ public static rest = <
1333
+ TOperation extends string,
1334
+ TIntegrations extends Record<TOperation, TDefaultIntegration>,
1335
+ TDefaultIntegrationProps extends object,
1336
+ TDefaultIntegration extends RestApiIntegration,
1337
+ >(
1338
+ options: IntegrationBuilderProps<
1339
+ TOperation,
1340
+ RestApiIntegration,
1341
+ TDefaultIntegrationProps,
1342
+ TDefaultIntegration
1343
+ >,
1344
+ ) => {
1345
+ return new IntegrationBuilder<
1346
+ TOperation,
1347
+ RestApiIntegration,
1348
+ TIntegrations,
1349
+ TDefaultIntegrationProps,
1350
+ TDefaultIntegration
1351
+ >(options);
1352
+ };
1353
+
1354
+ private constructor(
1355
+ options: IntegrationBuilderProps<
1356
+ TOperation,
1357
+ TBaseIntegration,
1358
+ TDefaultIntegrationProps,
1359
+ TDefaultIntegration
1360
+ >,
1361
+ ) {
1362
+ this.options = options;
1363
+ }
1364
+
1365
+ /**
1366
+ * Overrides default integrations with custom implementations for specific operations.
1367
+ *
1368
+ * @param overrides - Map of operation names to their custom integration implementations
1369
+ * @returns The builder instance with updated type information reflecting the overrides
1370
+ */
1371
+ public withOverrides<
1372
+ TOverrideIntegrations extends Partial<Record<TOperation, TBaseIntegration>>,
1373
+ >(overrides: TOverrideIntegrations) {
1374
+ this.integrations = { ...this.integrations, ...overrides };
1375
+ // Re-type to include the overridden integration types
1376
+ return this as unknown as IntegrationBuilder<
1377
+ TOperation,
1378
+ TBaseIntegration,
1379
+ Omit<TIntegrations, keyof TOverrideIntegrations> & TOverrideIntegrations,
1380
+ TDefaultIntegrationProps,
1381
+ TDefaultIntegration
1382
+ >;
1383
+ }
1384
+
1385
+ /**
1386
+ * Updates the default integration options that will be used for operations
1387
+ * without custom overrides.
1388
+ *
1389
+ * @param options - Partial default integration options to merge with existing defaults
1390
+ * @returns The builder instance
1391
+ */
1392
+ public withDefaultOptions(options: Partial<TDefaultIntegrationProps>) {
1393
+ this.options.defaultIntegrationOptions = {
1394
+ ...this.options.defaultIntegrationOptions,
1395
+ ...options,
1396
+ };
1397
+ return this;
1398
+ }
1399
+
1400
+ /**
1401
+ * Builds and returns the complete set of integrations.
1402
+ *
1403
+ * This method creates the final integration map by:
1404
+ * 1. Including all custom overrides provided via withOverrides()
1405
+ * 2. Creating default integrations for any operations without custom overrides
1406
+ *
1407
+ * @returns A complete map of operation names to their integrations
1408
+ */
1409
+ public build(): TIntegrations {
1410
+ return {
1411
+ ...this.integrations,
1412
+ ...Object.fromEntries(
1413
+ (Object.keys(this.options.operations) as TOperation[])
1414
+ .filter(
1415
+ (op) => !this.integrations[op as keyof typeof this.integrations],
1416
+ )
1417
+ .map((op) => [
1418
+ op,
1419
+ this.options.buildDefaultIntegration(
1420
+ op,
1421
+ this.options.defaultIntegrationOptions,
1422
+ ),
1423
+ ]),
1424
+ ),
1425
+ } as unknown as TIntegrations;
1426
+ }
1427
+ }
1428
+ "
1429
+ `;