Skip to content

Commit 05e8a92

Browse files
authored
feat(serverless): ability to add custom platform context (#574)
1 parent 2b31915 commit 05e8a92

5 files changed

Lines changed: 143 additions & 20 deletions

File tree

.changeset/soft-mayflies-tickle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ts-rest/serverless': minor
3+
---
4+
5+
Add ability to include custom platform context for serverless fetch handler

libs/ts-rest/serverless/src/lib/handlers/ts-rest-fetch.spec.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { initContract } from '@ts-rest/core';
22
import { parse as parseMultipart, getBoundary } from 'parse-multipart-data';
33
import { z } from 'zod';
44
import { vi } from 'vitest';
5-
import { fetchRequestHandler } from './ts-rest-fetch';
5+
import { fetchRequestHandler, tsr } from './ts-rest-fetch';
66
import { TsRestRequest } from '../request';
77

88
const c = initContract();
@@ -295,4 +295,62 @@ describe('fetchRequestHandler', () => {
295295
expect(response.headers).toEqual(expectedResponse.headers);
296296
expect(await response.json()).toEqual({ message: 'Server Error' });
297297
});
298+
299+
it('should handle custom context', async () => {
300+
type PlatformContext = {
301+
foo: string;
302+
};
303+
304+
const fetchRequestHandlerWithContext = (
305+
request: Request,
306+
platformContext: PlatformContext,
307+
) => {
308+
const contract = c.router({
309+
test: {
310+
method: 'GET',
311+
path: '/test',
312+
responses: {
313+
200: z.object({}).passthrough(),
314+
},
315+
},
316+
});
317+
318+
const router = tsr.platformContext<PlatformContext>().router(contract, {
319+
test: async (_, { foo }) => {
320+
return {
321+
status: 200,
322+
body: {
323+
foo,
324+
},
325+
};
326+
},
327+
});
328+
329+
return fetchRequestHandler({
330+
contract,
331+
router,
332+
options: {},
333+
request,
334+
platformContext,
335+
});
336+
};
337+
338+
const request = new Request('http://localhost/test', {
339+
method: 'GET',
340+
headers: { origin: 'http://localhost' },
341+
});
342+
343+
const response = await fetchRequestHandlerWithContext(request, {
344+
foo: 'context-bar',
345+
});
346+
const expectedResponse = new Response('{"foo":"context-bar"}', {
347+
headers: {
348+
'content-type': 'application/json',
349+
},
350+
});
351+
352+
expect(response.status).toEqual(expectedResponse.status);
353+
expect(response.headers).toEqual(expectedResponse.headers);
354+
expect(await response.json()).toEqual(await expectedResponse.json());
355+
});
298356
});
Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,70 @@
1-
import { AppRouter } from '@ts-rest/core';
1+
import { AppRoute, AppRouter } from '@ts-rest/core';
22
import { createServerlessRouter } from '../router';
3-
import { RecursiveRouterObj, ServerlessHandlerOptions } from '../types';
3+
import {
4+
AppRouteImplementationOrOptions,
5+
RecursiveRouterObj,
6+
ServerlessHandlerOptions,
7+
} from '../types';
48
import { TsRestRequest } from '../request';
59

610
export const tsr = {
7-
router: <T extends AppRouter, TRequestExtension>(
11+
router: <T extends AppRouter, TPlatformContext = {}, TRequestExtension = {}>(
812
contract: T,
9-
router: RecursiveRouterObj<T, {}, TRequestExtension>,
13+
router: RecursiveRouterObj<T, TPlatformContext, TRequestExtension>,
1014
) => router,
15+
route: <T extends AppRoute, TPlatformContext = {}, TRequestExtension = {}>(
16+
contractEndpoint: T,
17+
route: AppRouteImplementationOrOptions<
18+
T,
19+
TPlatformContext,
20+
TRequestExtension
21+
>,
22+
) => route,
23+
platformContext: <TPlatformContext>() => ({
24+
router: <T extends AppRouter, TRequestExtension = {}>(
25+
contract: T,
26+
router: RecursiveRouterObj<T, TPlatformContext, TRequestExtension>,
27+
) => router,
28+
route: <T extends AppRoute, TRequestExtension = {}>(
29+
contractEndpoint: T,
30+
route: AppRouteImplementationOrOptions<
31+
T,
32+
TPlatformContext,
33+
TRequestExtension
34+
>,
35+
) => route,
36+
}),
1137
};
1238

13-
export type FetchHandlerOptions<TRequestExtension = {}> =
14-
ServerlessHandlerOptions<{}, TRequestExtension>;
39+
export type FetchHandlerOptions<
40+
TPlatformContext = {},
41+
TRequestExtension = {},
42+
> = ServerlessHandlerOptions<TPlatformContext, TRequestExtension>;
1543

16-
export const fetchRequestHandler = <T extends AppRouter, TRequestExtension>({
44+
export const fetchRequestHandler = <
45+
T extends AppRouter,
46+
TRequestExtension,
47+
TPlatformContext = {},
48+
>({
1749
contract,
1850
router,
1951
options = {},
2052
request,
53+
platformContext,
2154
}: {
2255
contract: T;
23-
router: RecursiveRouterObj<T, {}, TRequestExtension>;
24-
options: FetchHandlerOptions<TRequestExtension>;
56+
router: RecursiveRouterObj<T, TPlatformContext, TRequestExtension>;
57+
options: FetchHandlerOptions<TPlatformContext, TRequestExtension>;
2558
request: Request;
59+
platformContext?: TPlatformContext;
2660
}) => {
27-
const serverlessRouter = createServerlessRouter<T, {}, TRequestExtension>(
28-
contract,
29-
router,
30-
options as ServerlessHandlerOptions,
31-
);
61+
const serverlessRouter = createServerlessRouter<
62+
T,
63+
TPlatformContext,
64+
TRequestExtension
65+
>(contract, router, options as ServerlessHandlerOptions<TPlatformContext>);
3266
const tsRestRequest = new TsRestRequest(request);
33-
return serverlessRouter.fetch(tsRestRequest, {});
67+
return serverlessRouter.fetch(tsRestRequest, {
68+
...platformContext,
69+
});
3470
};

libs/ts-rest/serverless/src/lib/handlers/ts-rest-lambda.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import type { Context } from 'aws-lambda';
2-
import { AppRouter } from '@ts-rest/core';
2+
import { AppRoute, AppRouter } from '@ts-rest/core';
33
import {
44
ApiGatewayEvent,
55
ApiGatewayResponse,
66
requestFromEvent,
77
responseToResult,
88
} from '../mappers/aws/api-gateway';
99
import { createServerlessRouter } from '../router';
10-
import { RecursiveRouterObj, ServerlessHandlerOptions } from '../types';
10+
import {
11+
AppRouteImplementationOrOptions,
12+
RecursiveRouterObj,
13+
ServerlessHandlerOptions,
14+
} from '../types';
1115

1216
type LambdaPlatformArgs = {
1317
rawEvent: ApiGatewayEvent;
@@ -19,6 +23,14 @@ export const tsr = {
1923
contract: T,
2024
router: RecursiveRouterObj<T, LambdaPlatformArgs, TRequestExtension>,
2125
) => router,
26+
route: <T extends AppRoute, TRequestExtension = {}>(
27+
contractEndpoint: T,
28+
route: AppRouteImplementationOrOptions<
29+
T,
30+
LambdaPlatformArgs,
31+
TRequestExtension
32+
>,
33+
) => route,
2234
};
2335

2436
export type LambdaHandlerOptions<TRequestExtension = {}> =

libs/ts-rest/serverless/src/lib/handlers/ts-rest-next.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { AppRouter } from '@ts-rest/core';
1+
import { AppRoute, AppRouter } from '@ts-rest/core';
22
import type { NextRequest, NextResponse } from 'next/server';
33
import { createServerlessRouter } from '../router';
4-
import { RecursiveRouterObj, ServerlessHandlerOptions } from '../types';
4+
import {
5+
AppRouteImplementationOrOptions,
6+
RecursiveRouterObj,
7+
ServerlessHandlerOptions,
8+
} from '../types';
59
import { TsRestRequest } from '../request';
610

711
type NextPlatformArgs = {
@@ -13,6 +17,14 @@ export const tsr = {
1317
contract: T,
1418
router: RecursiveRouterObj<T, NextPlatformArgs, TRequestExtension>,
1519
) => router,
20+
route: <T extends AppRoute, TRequestExtension = {}>(
21+
contractEndpoint: T,
22+
route: AppRouteImplementationOrOptions<
23+
T,
24+
NextPlatformArgs,
25+
TRequestExtension
26+
>,
27+
) => route,
1628
};
1729

1830
export type NextHandlerOptions<TRequestExtension> = ServerlessHandlerOptions<

0 commit comments

Comments
 (0)