@aws/nx-plugin 0.77.0 → 0.78.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 (26) hide show
  1. package/LICENSE-THIRD-PARTY +36 -8
  2. package/package.json +1 -1
  3. package/src/py/fast-api/__snapshots__/generator.spec.ts.snap +16 -13
  4. package/src/py/fast-api/react/__snapshots__/generator.spec.ts.snap +1 -1
  5. package/src/smithy/react-connection/__snapshots__/generator.spec.ts.snap +2 -2
  6. package/src/smithy/ts/api/__snapshots__/generator.spec.ts.snap +23 -17
  7. package/src/trpc/backend/__snapshots__/generator.spec.ts.snap +60 -34
  8. package/src/trpc/backend/files/src/handler.ts.template +48 -0
  9. package/src/trpc/backend/files/src/index.ts.template +1 -1
  10. package/src/trpc/backend/files/src/router.ts.template +0 -38
  11. package/src/trpc/backend/files/src/schema/z-async-iterable.ts.template +115 -0
  12. package/src/trpc/backend/generator.js +6 -2
  13. package/src/trpc/backend/generator.js.map +1 -1
  14. package/src/trpc/react/__snapshots__/generator.spec.ts.snap +274 -27
  15. package/src/trpc/react/files/src/components/__apiNameClassName__ClientProvider.tsx.template +57 -1
  16. package/src/trpc/react/generator.js +11 -1
  17. package/src/trpc/react/generator.js.map +1 -1
  18. package/src/utils/api-constructs/files/cdk/app/apis/rest/__apiNameKebabCase__.ts.template +13 -1
  19. package/src/utils/api-constructs/files/cdk/core/api/trpc/trpc-utils.ts.template +3 -3
  20. package/src/utils/api-constructs/files/terraform/app/apis/rest/__apiNameKebabCase__/__apiNameKebabCase__.tf.template +8 -3
  21. package/src/utils/api-constructs/files/terraform/core/api/rest/rest-api/rest-api.tf.template +1 -1
  22. package/src/utils/connection/open-api/files/components/__apiNameClassName__Provider.tsx.template +1 -1
  23. package/src/utils/files/website/hooks/sigv4/useSigV4.tsx.template +81 -33
  24. package/src/utils/versions.d.ts +3 -1
  25. package/src/utils/versions.js +2 -0
  26. package/src/utils/versions.js.map +1 -1
@@ -1,10 +1,5 @@
1
- import {
2
- awsLambdaRequestHandler,
3
- CreateAWSLambdaContextOptions,
4
- } from '@trpc/server/adapters/aws-lambda';
5
1
  import { echo } from './procedures/echo.js';
6
2
  import { t } from './init.js';
7
- import type { <%- apiGatewayEventType %> } from 'aws-lambda';
8
3
 
9
4
  export const router = t.router;
10
5
 
@@ -12,37 +7,4 @@ export const appRouter = router({
12
7
  echo,
13
8
  });
14
9
 
15
- export const handler = awsLambdaRequestHandler({
16
- router: appRouter,
17
- createContext: (ctx: CreateAWSLambdaContextOptions<<%- apiGatewayEventType %>>) => ctx,
18
- <%_ if (computeType === 'ServerlessApiGatewayRestApi') { _%>
19
- responseMeta: ({ ctx }) => ({
20
- headers: {
21
- 'Access-Control-Allow-Origin': getAllowedOrigin(ctx?.event),
22
- 'Access-Control-Allow-Methods': '*',
23
- },
24
- }),
25
- <%_ } _%>
26
- });
27
-
28
- <%_ if (computeType === 'ServerlessApiGatewayRestApi') { _%>
29
- /**
30
- * Restricts CORS origins to localhost and the domains specified in
31
- * the ALLOWED_ORIGINS environment variable if set, or * otherwise.
32
- * Customise using `restrictCorsTo` in your API CDK construct
33
- */
34
- const getAllowedOrigin = (event: <%- apiGatewayEventType %> | undefined) => {
35
- const origin = event?.headers?.origin ?? event?.headers?.Origin;
36
- const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') ?? [];
37
- const isLocalHost =
38
- origin && new Set(['localhost', '127.0.0.1']).has(new URL(origin).hostname);
39
- const isAllowedOrigin = origin && allowedOrigins.includes(origin);
40
- let corsOrigin = '*';
41
- if (allowedOrigins.length > 0 && !isLocalHost) {
42
- corsOrigin = isAllowedOrigin ? origin : allowedOrigins[0];
43
- }
44
- return corsOrigin;
45
- };
46
- <%_ } _%>
47
-
48
10
  export type AppRouter = typeof appRouter;
@@ -0,0 +1,115 @@
1
+ import { isTrackedEnvelope, tracked, type TrackedEnvelope } from '@trpc/server';
2
+ import { z } from 'zod';
3
+
4
+ function isAsyncIterable<TValue, TReturn = unknown>(
5
+ value: unknown,
6
+ ): value is AsyncIterable<TValue, TReturn> {
7
+ return !!value && typeof value === 'object' && Symbol.asyncIterator in value;
8
+ }
9
+
10
+ /**
11
+ * A Zod schema helper designed specifically for validating async iterables. This schema ensures that:
12
+ * 1. The value being validated is an async iterable.
13
+ * 2. Each item yielded by the async iterable conforms to a specified type.
14
+ * 3. The return value of the async iterable, if any, also conforms to a specified type.
15
+ */
16
+
17
+ // Non-tracked overload
18
+ export function ZodAsyncIterable<
19
+ TYieldIn,
20
+ TYieldOut,
21
+ TReturnIn = void,
22
+ TReturnOut = void,
23
+ >(opts: {
24
+ yield: z.ZodType<TYieldOut, TYieldIn>;
25
+ return?: z.ZodType<TReturnOut, TReturnIn>;
26
+ tracked?: false;
27
+ }): z.ZodPipe<
28
+ z.ZodCustom<
29
+ AsyncIterable<TYieldIn, TReturnIn>,
30
+ AsyncIterable<TYieldIn, TReturnIn>
31
+ >,
32
+ z.ZodTransform<
33
+ AsyncGenerator<
34
+ Awaited<TYieldOut>,
35
+ Awaited<TReturnOut> | undefined,
36
+ unknown
37
+ >,
38
+ AsyncIterable<TYieldIn, TReturnIn>
39
+ >
40
+ >;
41
+
42
+ // Tracked overload
43
+ export function ZodAsyncIterable<
44
+ TYieldIn,
45
+ TYieldOut,
46
+ TReturnIn = void,
47
+ TReturnOut = void,
48
+ >(opts: {
49
+ yield: z.ZodType<TYieldOut, TYieldIn>;
50
+ return?: z.ZodType<TReturnOut, TReturnIn>;
51
+ tracked: true;
52
+ }): z.ZodPipe<
53
+ z.ZodCustom<
54
+ AsyncIterable<TrackedEnvelope<TYieldIn>, TReturnIn>,
55
+ AsyncIterable<TrackedEnvelope<TYieldIn>, TReturnIn>
56
+ >,
57
+ z.ZodTransform<
58
+ AsyncGenerator<
59
+ TrackedEnvelope<Awaited<TYieldOut>>,
60
+ Awaited<TReturnOut> | undefined,
61
+ unknown
62
+ >,
63
+ AsyncIterable<TrackedEnvelope<TYieldIn>, TReturnIn>
64
+ >
65
+ >;
66
+
67
+ // Implementation
68
+ export function ZodAsyncIterable<
69
+ TYieldIn,
70
+ TYieldOut,
71
+ TReturnIn = void,
72
+ TReturnOut = void,
73
+ >(opts: {
74
+ /**
75
+ * Validate the value yielded by the async generator
76
+ */
77
+ yield: z.ZodType<TYieldOut, TYieldIn>;
78
+ /**
79
+ * Validate the return value of the async generator
80
+ * @remark not applicable for subscriptions
81
+ */
82
+ return?: z.ZodType<TReturnOut, TReturnIn>;
83
+ /**
84
+ * Whether if the yielded values are tracked
85
+ * @remark only applicable for subscriptions
86
+ */
87
+ tracked?: boolean;
88
+ }) {
89
+ return z
90
+ .custom<AsyncIterable<TYieldIn | TrackedEnvelope<TYieldIn>, TReturnIn>>(
91
+ (val) => isAsyncIterable(val),
92
+ )
93
+ .transform(async function* (iter) {
94
+ const iterator = iter[Symbol.asyncIterator]();
95
+ try {
96
+ let next;
97
+ while ((next = await iterator.next()) && !next.done) {
98
+ if (opts.tracked) {
99
+ const [id, data] = z
100
+ .custom<TrackedEnvelope<unknown>>(isTrackedEnvelope)
101
+ .parse(next.value);
102
+ yield tracked(id, await opts.yield.parseAsync(data));
103
+ continue;
104
+ }
105
+ yield opts.yield.parseAsync(next.value);
106
+ }
107
+ if (opts.return) {
108
+ return await opts.return.parseAsync(next.value);
109
+ }
110
+ return;
111
+ } finally {
112
+ await iterator.return?.();
113
+ }
114
+ });
115
+ }
@@ -56,7 +56,7 @@ function tsTrpcApiGenerator(tree, options) {
56
56
  auth: options.auth,
57
57
  iacProvider,
58
58
  });
59
- projectConfig.metadata = Object.assign(Object.assign({}, projectConfig.metadata), { apiName: options.name, apiType: 'trpc', auth: options.auth });
59
+ projectConfig.metadata = Object.assign(Object.assign({}, projectConfig.metadata), { apiName: options.name, apiType: 'trpc', auth: options.auth, computeType: options.computeType });
60
60
  projectConfig.targets.serve = {
61
61
  executor: 'nx:run-commands',
62
62
  options: {
@@ -66,7 +66,7 @@ function tsTrpcApiGenerator(tree, options) {
66
66
  continuous: true,
67
67
  };
68
68
  (0, bundle_1.addTypeScriptBundleTarget)(tree, projectConfig, {
69
- targetFilePath: 'src/router.ts',
69
+ targetFilePath: 'src/handler.ts',
70
70
  external: [/@aws-sdk\/.*/], // lambda runtime provides aws sdk
71
71
  });
72
72
  (0, nx_1.addDependencyToTargetIfNotPresent)(projectConfig, 'build', 'bundle');
@@ -76,6 +76,10 @@ function tsTrpcApiGenerator(tree, options) {
76
76
  overwriteStrategy: devkit_1.OverwriteStrategy.Overwrite,
77
77
  });
78
78
  tree.delete((0, devkit_1.joinPathFragments)(backendRoot, 'src', 'lib'));
79
+ // Remove streaming schema helper for HTTP APIs (API Gateway HTTP API doesn't support streaming)
80
+ if (options.computeType !== 'ServerlessApiGatewayRestApi') {
81
+ tree.delete((0, devkit_1.joinPathFragments)(backendRoot, 'src', 'schema', 'z-async-iterable.ts'));
82
+ }
79
83
  (0, devkit_1.addDependenciesToPackageJson)(tree, (0, versions_1.withVersions)([
80
84
  'aws-xray-sdk-core',
81
85
  'zod',
@@ -1 +1 @@
1
- {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../packages/nx-plugin/src/trpc/backend/generator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../../../../packages/nx-plugin/src/trpc/backend/generator.ts"],"names":[],"mappings":";;;AAsCA,gDAiIC;;AAvKD;;;GAGG;AACH,uCASoB;AAEpB,qEAA0E;AAC1E,+EAAwD;AACxD,qDAAwE;AACxE,mDAAoD;AACpD,6CAA2D;AAC3D,+CAA0D;AAC1D,+CAAoD;AACpD,uCAMwB;AACxB,iDAAsE;AACtE,8EAA+E;AAC/E,2CAA8C;AAC9C,yCAAqD;AACrD,sDAAsE;AAEzD,QAAA,2BAA2B,GACtC,IAAA,qBAAgB,EAAC,UAAU,CAAC,CAAC;AAE/B,SAAsB,kBAAkB,CACtC,IAAU,EACV,OAAiC;;QAEjC,MAAM,WAAW,GAAG,MAAM,IAAA,wBAAkB,EAAC,IAAI,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QAExE,MAAM,IAAA,6CAAyB,EAAC,IAAI,EAAE;YACpC,WAAW;SACZ,CAAC,CAAC;QAEH,MAAM,YAAY,GAAG,IAAA,6BAAiB,EAAC,IAAI,CAAC,CAAC;QAC7C,MAAM,gBAAgB,GAAG,IAAA,iBAAS,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACjD,MAAM,gBAAgB,GAAG,IAAA,mBAAW,EAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAEnD,MAAM,WAAW,GAAG,gBAAgB,CAAC;QACrC,MAAM,kBAAkB,GAAG,GAAG,YAAY,GAAG,WAAW,EAAE,CAAC;QAE3D,MAAM,IAAA,mBAAkB,EAAC,IAAI,EAAE;YAC7B,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CAAC;QAEH,MAAM,aAAa,GAAG,IAAA,wCAAmC,EACvD,IAAI,EACJ,kBAAkB,CACnB,CAAC;QACF,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC;QAEvC,MAAM,IAAI,GAAG,IAAA,iBAAU,EAAC,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QAEnD,MAAM,eAAe,mBACnB,kBAAkB,EAClB,mBAAmB,EAAE,IAAA,wBAAY,EAAC,kBAAkB,CAAC,EACrD,gBAAgB;YAChB,gBAAgB;YAChB,WAAW,EACX,SAAS,EAAE,IAAA,iCAAwB,GAAE,CAAC,IAAI,EAC1C,mBAAmB,EAAE,sBAAsB,CAAC,OAAO,CAAC,EACpD,IAAI,IACD,OAAO,CACX,CAAC;QAEF,IAAA,mCAAkB,EAAC,IAAI,EAAE;YACvB,cAAc,EAAE,kBAAkB;YAClC,gBAAgB;YAChB,gBAAgB;YAChB,aAAa,EACX,OAAO,CAAC,WAAW,KAAK,6BAA6B,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YACzE,OAAO,EAAE;gBACP,IAAI,EAAE,MAAM;gBACZ,YAAY,EAAE,eAAe,CAAC,mBAAmB;gBACjD,eAAe,EAAE,IAAA,0BAAiB,EAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,CAAC;aAClE;YACD,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,WAAW;SACZ,CAAC,CAAC;QAEH,aAAa,CAAC,QAAQ,GAAG,gCACpB,aAAa,CAAC,QAAQ,KACzB,OAAO,EAAE,OAAO,CAAC,IAAI,EACrB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,OAAO,CAAC,IAAI,EAClB,WAAW,EAAE,OAAO,CAAC,WAAW,GACtB,CAAC;QAEb,aAAa,CAAC,OAAO,CAAC,KAAK,GAAG;YAC5B,QAAQ,EAAE,iBAAiB;YAC3B,OAAO,EAAE;gBACP,QAAQ,EAAE,CAAC,iCAAiC,CAAC;gBAC7C,GAAG,EAAE,eAAe;aACrB;YACD,UAAU,EAAE,IAAI;SACjB,CAAC;QAEF,IAAA,kCAAyB,EAAC,IAAI,EAAE,aAAa,EAAE;YAC7C,cAAc,EAAE,gBAAgB;YAChC,QAAQ,EAAE,CAAC,cAAc,CAAC,EAAE,kCAAkC;SAC/D,CAAC,CAAC;QAEH,IAAA,sCAAiC,EAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;QAEpE,aAAa,CAAC,OAAO,GAAG,IAAA,uBAAc,EAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAE9D,IAAA,mCAA0B,EAAC,IAAI,EAAE,aAAa,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QAEpE,IAAA,sBAAa,EACX,IAAI,EACJ,IAAA,0BAAiB,EAAC,SAAS,EAAE,OAAO,CAAC,EACrC,WAAW,EACX,eAAe,EACf;YACE,iBAAiB,EAAE,0BAAiB,CAAC,SAAS;SAC/C,CACF,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,IAAA,0BAAiB,EAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QAE1D,gGAAgG;QAChG,IAAI,OAAO,CAAC,WAAW,KAAK,6BAA6B,EAAE,CAAC;YAC1D,IAAI,CAAC,MAAM,CACT,IAAA,0BAAiB,EAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,qBAAqB,CAAC,CACvE,CAAC;QACJ,CAAC;QAED,IAAA,qCAA4B,EAC1B,IAAI,EACJ,IAAA,uBAAY,EAAC;YACX,mBAAmB;YACnB,KAAK;YACL,+BAA+B;YAC/B,gCAAgC;YAChC,+BAA+B;YAC/B,cAAc;YACd,cAAc;YACd,WAAW;YACX,+BAA+B;SAChC,CAAC,EACF,IAAA,uBAAY,EAAC,CAAC,mBAAmB,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAClE,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,IAAA,0BAAiB,EAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;QAE5D,IAAA,yBAAoB,EAAC,IAAI,EAAE,WAAW,EAAE,mCAA2B,CAAC,CAAC;QAErE,MAAM,IAAA,yCAA+B,EAAC,IAAI,EAAE,CAAC,mCAA2B,CAAC,CAAC,CAAC;QAE3E,MAAM,IAAA,6BAAoB,EAAC,IAAI,CAAC,CAAC;QACjC,OAAO,GAAG,EAAE;YACV,IAAA,4BAAmB,EAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC;IACJ,CAAC;CAAA;AAED,MAAM,sBAAsB,GAAG,CAAC,OAAiC,EAAU,EAAE;IAC3E,IAAI,OAAO,CAAC,WAAW,KAAK,6BAA6B,EAAE,CAAC;QAC1D,OAAO,sBAAsB,CAAC;IAChC,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QAC3B,OAAO,yCAAyC,CAAC;IACnD,CAAC;SAAM,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO,yCAAyC,CAAC;IACnD,CAAC;IACD,OAAO,wBAAwB,CAAC;AAClC,CAAC,CAAC;AAEF,kBAAe,kBAAkB,CAAC"}
@@ -1,5 +1,224 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
+ exports[`trpc react generator > REST API (ServerlessApiGatewayRestApi) > should generate REST API client provider with splitLink for Cognito auth > TestApiClientProvider-REST-Cognito.tsx 1`] = `
4
+ "import { AppRouter } from 'backend';
5
+ import { useQueryClient } from '@tanstack/react-query';
6
+ import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
7
+ import { createContext, FC, PropsWithChildren, useMemo } from 'react';
8
+ import { useRuntimeConfig } from '../hooks/useRuntimeConfig';
9
+ import {
10
+ TRPCClient,
11
+ createTRPCClient,
12
+ httpLink,
13
+ httpSubscriptionLink,
14
+ splitLink,
15
+ } from '@trpc/client';
16
+ import { EventSourcePolyfill } from 'event-source-polyfill';
17
+ import { useAuth } from 'react-oidc-context';
18
+
19
+ interface TestApiTRPCContextValue {
20
+ optionsProxy: ReturnType<typeof createTRPCOptionsProxy<AppRouter>>;
21
+ client: TRPCClient<AppRouter>;
22
+ }
23
+
24
+ export const TestApiTRPCContext = createContext<TestApiTRPCContextValue | null>(
25
+ null,
26
+ );
27
+
28
+ export const TestApiClientProvider: FC<PropsWithChildren> = ({ children }) => {
29
+ const queryClient = useQueryClient();
30
+ const runtimeConfig = useRuntimeConfig();
31
+ const apiUrl = runtimeConfig.apis.TestApi;
32
+ const auth = useAuth();
33
+ const user = auth?.user;
34
+
35
+ const container = useMemo<TestApiTRPCContextValue>(() => {
36
+ const client = createTRPCClient<AppRouter>({
37
+ links: [
38
+ splitLink({
39
+ condition: (op) => op.type === 'subscription',
40
+ true: httpSubscriptionLink({
41
+ url: apiUrl,
42
+ EventSource: EventSourcePolyfill,
43
+ eventSourceOptions: async ({ op }) => {
44
+ return {
45
+ headers: {
46
+ Authorization: \`Bearer \${user?.id_token}\`,
47
+ },
48
+ };
49
+ },
50
+ }),
51
+ false: httpLink({
52
+ url: apiUrl,
53
+ headers: {
54
+ Authorization: \`Bearer \${user?.id_token}\`,
55
+ },
56
+ }),
57
+ }),
58
+ ],
59
+ });
60
+
61
+ const optionsProxy = createTRPCOptionsProxy<AppRouter>({
62
+ client,
63
+ queryClient,
64
+ });
65
+
66
+ return { optionsProxy, client };
67
+ }, [apiUrl, queryClient, user]);
68
+
69
+ return (
70
+ <TestApiTRPCContext.Provider value={container}>
71
+ {children}
72
+ </TestApiTRPCContext.Provider>
73
+ );
74
+ };
75
+
76
+ export default TestApiClientProvider;
77
+ "
78
+ `;
79
+
80
+ exports[`trpc react generator > REST API (ServerlessApiGatewayRestApi) > should generate REST API client provider with splitLink for IAM auth > TestApiClientProvider-REST-IAM.tsx 1`] = `
81
+ "import { AppRouter } from 'backend';
82
+ import { useQueryClient } from '@tanstack/react-query';
83
+ import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
84
+ import { createContext, FC, PropsWithChildren, useMemo } from 'react';
85
+ import { useRuntimeConfig } from '../hooks/useRuntimeConfig';
86
+ import {
87
+ TRPCClient,
88
+ createTRPCClient,
89
+ httpLink,
90
+ httpSubscriptionLink,
91
+ splitLink,
92
+ } from '@trpc/client';
93
+ import { EventSourcePolyfill } from 'event-source-polyfill';
94
+ import { useSigV4 } from '../hooks/useSigV4';
95
+
96
+ interface TestApiTRPCContextValue {
97
+ optionsProxy: ReturnType<typeof createTRPCOptionsProxy<AppRouter>>;
98
+ client: TRPCClient<AppRouter>;
99
+ }
100
+
101
+ export const TestApiTRPCContext = createContext<TestApiTRPCContextValue | null>(
102
+ null,
103
+ );
104
+
105
+ export const TestApiClientProvider: FC<PropsWithChildren> = ({ children }) => {
106
+ const queryClient = useQueryClient();
107
+ const runtimeConfig = useRuntimeConfig();
108
+ const apiUrl = runtimeConfig.apis.TestApi;
109
+ const sigv4Client = useSigV4();
110
+
111
+ const container = useMemo<TestApiTRPCContextValue>(() => {
112
+ const client = createTRPCClient<AppRouter>({
113
+ links: [
114
+ splitLink({
115
+ condition: (op) => op.type === 'subscription',
116
+ true: httpSubscriptionLink({
117
+ url: apiUrl,
118
+ EventSource: EventSourcePolyfill,
119
+ eventSourceOptions: async ({ op }) => {
120
+ const url =
121
+ \`\${apiUrl.replace(/\\/$/, '')}/\${op.path}\` +
122
+ (op.input !== undefined
123
+ ? \`?input=\${encodeURIComponent(JSON.stringify(op.input))}\`
124
+ : '');
125
+ const signed = await sigv4Client.sign(url, { method: 'GET' });
126
+ const headers: Record<string, string> = {};
127
+ signed.headers.forEach((v, k) => {
128
+ headers[k] = v;
129
+ });
130
+ return { headers };
131
+ },
132
+ }),
133
+ false: httpLink({
134
+ url: apiUrl,
135
+ fetch: sigv4Client.fetch,
136
+ }),
137
+ }),
138
+ ],
139
+ });
140
+
141
+ const optionsProxy = createTRPCOptionsProxy<AppRouter>({
142
+ client,
143
+ queryClient,
144
+ });
145
+
146
+ return { optionsProxy, client };
147
+ }, [apiUrl, queryClient, sigv4Client]);
148
+
149
+ return (
150
+ <TestApiTRPCContext.Provider value={container}>
151
+ {children}
152
+ </TestApiTRPCContext.Provider>
153
+ );
154
+ };
155
+
156
+ export default TestApiClientProvider;
157
+ "
158
+ `;
159
+
160
+ exports[`trpc react generator > REST API (ServerlessApiGatewayRestApi) > should generate REST API client provider with splitLink for None auth > TestApiClientProvider-REST-None.tsx 1`] = `
161
+ "import { AppRouter } from 'backend';
162
+ import { useQueryClient } from '@tanstack/react-query';
163
+ import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
164
+ import { createContext, FC, PropsWithChildren, useMemo } from 'react';
165
+ import { useRuntimeConfig } from '../hooks/useRuntimeConfig';
166
+ import {
167
+ TRPCClient,
168
+ createTRPCClient,
169
+ httpLink,
170
+ httpSubscriptionLink,
171
+ splitLink,
172
+ } from '@trpc/client';
173
+
174
+ interface TestApiTRPCContextValue {
175
+ optionsProxy: ReturnType<typeof createTRPCOptionsProxy<AppRouter>>;
176
+ client: TRPCClient<AppRouter>;
177
+ }
178
+
179
+ export const TestApiTRPCContext = createContext<TestApiTRPCContextValue | null>(
180
+ null,
181
+ );
182
+
183
+ export const TestApiClientProvider: FC<PropsWithChildren> = ({ children }) => {
184
+ const queryClient = useQueryClient();
185
+ const runtimeConfig = useRuntimeConfig();
186
+ const apiUrl = runtimeConfig.apis.TestApi;
187
+
188
+ const container = useMemo<TestApiTRPCContextValue>(() => {
189
+ const client = createTRPCClient<AppRouter>({
190
+ links: [
191
+ splitLink({
192
+ condition: (op) => op.type === 'subscription',
193
+ true: httpSubscriptionLink({
194
+ url: apiUrl,
195
+ }),
196
+ false: httpLink({
197
+ url: apiUrl,
198
+ }),
199
+ }),
200
+ ],
201
+ });
202
+
203
+ const optionsProxy = createTRPCOptionsProxy<AppRouter>({
204
+ client,
205
+ queryClient,
206
+ });
207
+
208
+ return { optionsProxy, client };
209
+ }, [apiUrl, queryClient]);
210
+
211
+ return (
212
+ <TestApiTRPCContext.Provider value={container}>
213
+ {children}
214
+ </TestApiTRPCContext.Provider>
215
+ );
216
+ };
217
+
218
+ export default TestApiClientProvider;
219
+ "
220
+ `;
221
+
3
222
  exports[`trpc react generator > should generate trpc react files > TestApiClientProvider.tsx 1`] = `
4
223
  "import { AppRouter } from 'backend';
5
224
  import { useQueryClient } from '@tanstack/react-query';
@@ -172,7 +391,7 @@ export const TestApiClientProvider: FC<PropsWithChildren> = ({ children }) => {
172
391
  const container = useMemo<TestApiTRPCContextValue>(() => {
173
392
  const linkOptions: HTTPLinkOptions<any> = {
174
393
  url: apiUrl,
175
- fetch: sigv4Client,
394
+ fetch: sigv4Client.fetch,
176
395
  };
177
396
 
178
397
  const client = createTRPCClient<AppRouter>({
@@ -213,7 +432,17 @@ import {
213
432
  // Credential expiration grace time before considering credentials as expired
214
433
  const CREDENTIAL_EXPIRY_OFFSET_MILLIS = 30 * 1000;
215
434
 
216
- export const useSigV4 = () => {
435
+ type AwsSignInput = Parameters<AwsClient['sign']>[0];
436
+ type AwsSignInit = Parameters<AwsClient['sign']>[1];
437
+
438
+ export interface SigV4Client {
439
+ /** A SigV4-signing fetch drop-in replacement. */
440
+ fetch: typeof globalThis.fetch;
441
+ /** Signs a request. Same parameters as AwsClient.sign(). Returns an unsigned Request when signing is skipped. */
442
+ sign: AwsClient['sign'];
443
+ }
444
+
445
+ export const useSigV4 = (): SigV4Client => {
217
446
  const { cognitoProps } = useRuntimeConfig();
218
447
  const auth = useAuth();
219
448
  const user = auth?.user;
@@ -222,6 +451,8 @@ export const useSigV4 = () => {
222
451
  {},
223
452
  );
224
453
 
454
+ const skipSigning = !cognitoProps && import.meta.env.MODE === 'serve-local';
455
+
225
456
  const withCachedCredentials = useCallback(
226
457
  async (
227
458
  provider: AwsCredentialIdentityProvider,
@@ -244,37 +475,53 @@ export const useSigV4 = () => {
244
475
  [],
245
476
  );
246
477
 
247
- return useCallback(
478
+ const getAwsClient = useCallback(async (): Promise<AwsClient> => {
479
+ if (!cognitoProps) {
480
+ throw new Error('cognitoProps is undefined!');
481
+ }
482
+ if (!user?.id_token) {
483
+ throw new Error('user.id_token is undefined!');
484
+ }
485
+
486
+ const credentialsFromCognitoIdentityPool = fromCognitoIdentityPool({
487
+ client: new CognitoIdentityClient({ region: cognitoProps.region }),
488
+ identityPoolId: cognitoProps.identityPoolId,
489
+ logins: {
490
+ [\`cognito-idp.\${cognitoProps.region}.amazonaws.com/\${cognitoProps.userPoolId}\`]:
491
+ user.id_token,
492
+ },
493
+ });
494
+ const credential = await withCachedCredentials(
495
+ credentialsFromCognitoIdentityPool,
496
+ cognitoProps.identityPoolId,
497
+ user.profile.sub,
498
+ );
499
+ return new AwsClient(credential);
500
+ }, [cognitoProps, user?.id_token, user?.profile.sub, withCachedCredentials]);
501
+
502
+ const sigv4Fetch: typeof globalThis.fetch = useCallback(
248
503
  async (input: RequestInfo | URL, init?: RequestInit | undefined) => {
249
- if (!cognitoProps && import.meta.env.MODE === 'serve-local') {
250
- // Skip request signing in serve-local mode when cognitoProps are not set
504
+ if (skipSigning) {
251
505
  return fetch(input, init);
252
506
  }
253
- if (!cognitoProps) {
254
- throw new Error('cognitoProps is undefined!');
255
- }
256
- if (!user?.id_token) {
257
- throw new Error('user.id_token is undefined!');
258
- }
259
-
260
- const credentialsFromCognitoIdentityPool = fromCognitoIdentityPool({
261
- client: new CognitoIdentityClient({ region: cognitoProps.region }),
262
- identityPoolId: cognitoProps.identityPoolId,
263
- logins: {
264
- [\`cognito-idp.\${cognitoProps.region}.amazonaws.com/\${cognitoProps.userPoolId}\`]:
265
- user.id_token,
266
- },
267
- });
268
- const credential = await withCachedCredentials(
269
- credentialsFromCognitoIdentityPool,
270
- cognitoProps.identityPoolId,
271
- user.profile.sub,
272
- );
273
- const awsClient = new AwsClient(credential);
507
+ const awsClient = await getAwsClient();
274
508
  return awsClient.fetch(input, init);
275
509
  },
276
- [cognitoProps, user?.id_token, user?.profile.sub, withCachedCredentials],
510
+ [skipSigning, getAwsClient],
277
511
  );
512
+
513
+ const sign = useCallback(
514
+ async (input: AwsSignInput, init?: AwsSignInit): Promise<Request> => {
515
+ if (skipSigning) {
516
+ return new Request(input.toString(), init ?? undefined);
517
+ }
518
+ const awsClient = await getAwsClient();
519
+ return awsClient.sign(input, init);
520
+ },
521
+ [skipSigning, getAwsClient],
522
+ );
523
+
524
+ return { fetch: sigv4Fetch, sign };
278
525
  };
279
526
  "
280
527
  `;
@@ -3,7 +3,14 @@ import { useQueryClient } from "@tanstack/react-query";
3
3
  import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
4
4
  import { createContext, FC, PropsWithChildren, useMemo } from "react";
5
5
  import { useRuntimeConfig } from "../hooks/useRuntimeConfig";
6
+ <%_ if (isRestApi) { _%>
7
+ import { TRPCClient, createTRPCClient, httpLink, httpSubscriptionLink, splitLink } from "@trpc/client";
8
+ <%_ if (auth !== 'None') { _%>
9
+ import { EventSourcePolyfill } from "event-source-polyfill";
10
+ <%_ } _%>
11
+ <%_ } else { _%>
6
12
  import { HTTPLinkOptions, TRPCClient, createTRPCClient, httpLink } from "@trpc/client";
13
+ <%_ } _%>
7
14
  <%_ if (auth === 'IAM') { _%>
8
15
  import { useSigV4 } from "../hooks/useSigV4";
9
16
  <%_ } else if (auth === 'Cognito') { _%>
@@ -31,10 +38,58 @@ export const <%= apiNameClassName %>ClientProvider: FC<PropsWithChildren> = ({
31
38
  <%_ } _%>
32
39
 
33
40
  const container = useMemo<<%= apiNameClassName %>TRPCContextValue>(() => {
41
+ <%_ if (isRestApi) { _%>
42
+ const client = createTRPCClient<AppRouter>({
43
+ links: [
44
+ splitLink({
45
+ condition: (op) => op.type === 'subscription',
46
+ true: httpSubscriptionLink({
47
+ url: apiUrl,
48
+ <%_ if (auth !== 'None') { _%>
49
+ EventSource: EventSourcePolyfill,
50
+ eventSourceOptions: async ({ op }) => {
51
+ <%_ if (auth === 'IAM') { _%>
52
+ const url =
53
+ `${apiUrl.replace(/\/$/, '')}/${op.path}` +
54
+ (op.input !== undefined
55
+ ? `?input=${encodeURIComponent(JSON.stringify(op.input))}`
56
+ : '');
57
+ const signed = await sigv4Client.sign(url, { method: 'GET' });
58
+ const headers: Record<string, string> = {};
59
+ signed.headers.forEach((v, k) => {
60
+ headers[k] = v;
61
+ });
62
+ return { headers };
63
+ <%_ } else if (auth === 'Cognito') { _%>
64
+ return {
65
+ headers: {
66
+ Authorization: `Bearer ${user?.id_token}`,
67
+ },
68
+ };
69
+ <%_ } else { _%>
70
+ return {};
71
+ <%_ } _%>
72
+ },
73
+ <%_ } _%>
74
+ }),
75
+ false: httpLink({
76
+ url: apiUrl,
77
+ <%_ if (auth === 'IAM') { _%>
78
+ fetch: sigv4Client.fetch,
79
+ <%_ } else if (auth === 'Cognito') { _%>
80
+ headers: {
81
+ Authorization: `Bearer ${user?.id_token}`,
82
+ },
83
+ <%_ } _%>
84
+ }),
85
+ }),
86
+ ],
87
+ });
88
+ <%_ } else { _%>
34
89
  const linkOptions: HTTPLinkOptions<any> = {
35
90
  url: apiUrl,
36
91
  <%_ if (auth === 'IAM') { _%>
37
- fetch: sigv4Client,
92
+ fetch: sigv4Client.fetch,
38
93
  <%_ } else if (auth === 'Cognito') { _%>
39
94
  headers: {
40
95
  Authorization: `Bearer ${user?.id_token}`,
@@ -45,6 +100,7 @@ export const <%= apiNameClassName %>ClientProvider: FC<PropsWithChildren> = ({
45
100
  const client = createTRPCClient<AppRouter>({
46
101
  links: [httpLink(linkOptions)],
47
102
  });
103
+ <%_ } _%>
48
104
 
49
105
  const optionsProxy = createTRPCOptionsProxy<AppRouter>({
50
106
  client,
@@ -28,9 +28,11 @@ function reactGenerator(tree, options) {
28
28
  const apiName = metadata.apiName;
29
29
  const auth = (_a = metadata.auth) !== null && _a !== void 0 ? _a : 'IAM';
30
30
  const port = (_d = (_b = metadata.port) !== null && _b !== void 0 ? _b : (_c = metadata.ports) === null || _c === void 0 ? void 0 : _c[0]) !== null && _d !== void 0 ? _d : 2022;
31
+ const isRestApi = metadata.computeType === 'ServerlessApiGatewayRestApi';
31
32
  const apiNameClassName = (0, names_1.toClassName)(apiName);
32
33
  const backendProjectAlias = (0, npm_scope_1.toScopeAlias)(backendProjectConfig.name);
33
34
  (0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, 'files'), frontendProjectConfig.root, Object.assign(Object.assign({ apiName, apiNameClassName: (0, names_1.toClassName)(apiName) }, options), { auth,
35
+ isRestApi,
34
36
  backendProjectAlias }), {
35
37
  overwriteStrategy: devkit_1.OverwriteStrategy.KeepExisting,
36
38
  });
@@ -70,6 +72,9 @@ function reactGenerator(tree, options) {
70
72
  '@trpc/tanstack-react-query',
71
73
  '@tanstack/react-query',
72
74
  '@tanstack/react-query-devtools',
75
+ ...(isRestApi && auth !== 'None'
76
+ ? ['event-source-polyfill']
77
+ : []),
73
78
  ...(auth === 'IAM'
74
79
  ? [
75
80
  'oidc-client-ts',
@@ -80,7 +85,12 @@ function reactGenerator(tree, options) {
80
85
  ]
81
86
  : []),
82
87
  ...(auth === 'Cognito' ? ['react-oidc-context'] : []),
83
- ]), (0, versions_1.withVersions)(['@smithy/types']));
88
+ ]), (0, versions_1.withVersions)([
89
+ '@smithy/types',
90
+ ...(isRestApi && auth !== 'None'
91
+ ? ['@types/event-source-polyfill']
92
+ : []),
93
+ ]));
84
94
  yield (0, metrics_1.addGeneratorMetricsIfApplicable)(tree, [exports.TRPC_REACT_GENERATOR_INFO]);
85
95
  yield (0, format_1.formatFilesInSubtree)(tree);
86
96
  return () => {