@@ -15,26 +15,46 @@ import type {
1515} from 'fastify'
1616import { terminalLink } from 'termi-link'
1717
18+ import type {
19+ CedarHandler ,
20+ CedarRouteRecord ,
21+ LegacyHandler ,
22+ } from '@cedarjs/api/runtime'
23+ import { buildCedarContext , wrapLegacyHandler } from '@cedarjs/api/runtime'
1824import { getPaths } from '@cedarjs/project-config'
1925
2026import { requestHandler } from '../requestHandlers/awsLambdaFastify.js'
2127import { escape } from '../utils.js'
2228
23- export type Lambdas = Record < string , Handler >
24- export const LAMBDA_FUNCTIONS : Lambdas = { }
29+ export const LAMBDA_FUNCTIONS = new Map < string , Handler > ( )
30+ export const CEDAR_HANDLERS = new Map < string , CedarHandler > ( )
31+ const cedarRouteManifest : CedarRouteRecord [ ] = [ ]
32+
33+ /**
34+ * Exports a copy of the Cedar route manifest.
35+ *
36+ * This is intended to be used to later build WinterTC compatible `fetch`
37+ * exports
38+ */
39+ export const getCedarRouteManifest = ( ) => [ ...cedarRouteManifest ]
2540
26- // Import the API functions and add them to the LAMBDA_FUNCTIONS object
41+ // Import the API functions and add them to the LAMBDA_FUNCTIONS map
2742
2843export const setLambdaFunctions = async ( foundFunctions : string [ ] ) => {
2944 const tsImport = Date . now ( )
3045 console . log ( ansis . dim . italic ( 'Importing Server Functions... ' ) )
3146
47+ cedarRouteManifest . length = 0
48+ LAMBDA_FUNCTIONS . clear ( )
49+ CEDAR_HANDLERS . clear ( )
50+
3251 const imports = foundFunctions . map ( async ( fnPath ) => {
3352 const ts = Date . now ( )
3453 const routeName = path . basename ( fnPath ) . replace ( '.js' , '' )
54+ const routePath = routeName === 'graphql' ? '/graphql' : `/${ routeName } `
3555
3656 const fnImport = await import ( pathToFileURL ( fnPath ) . href )
37- const handler : Handler = ( ( ) => {
57+ const handler : Handler | undefined = ( ( ) => {
3858 if ( 'handler' in fnImport ) {
3959 // ESModule export of handler - when using
4060 // `export const handler = ...` - most common case
@@ -58,15 +78,60 @@ export const setLambdaFunctions = async (foundFunctions: string[]) => {
5878 return undefined
5979 } ) ( )
6080
61- LAMBDA_FUNCTIONS [ routeName ] = handler
62- if ( ! handler ) {
81+ const cedarHandler : CedarHandler | undefined = ( ( ) => {
82+ if (
83+ 'handleRequest' in fnImport &&
84+ typeof fnImport . handleRequest === 'function'
85+ ) {
86+ return fnImport . handleRequest as CedarHandler
87+ }
88+
89+ if (
90+ 'default' in fnImport &&
91+ fnImport . default &&
92+ 'handleRequest' in fnImport . default &&
93+ typeof fnImport . default . handleRequest === 'function'
94+ ) {
95+ return fnImport . default . handleRequest as CedarHandler
96+ }
97+
98+ return undefined
99+ } ) ( )
100+
101+ if ( handler ) {
102+ LAMBDA_FUNCTIONS . set ( routeName , handler )
103+ }
104+
105+ if ( cedarHandler ) {
106+ CEDAR_HANDLERS . set ( routeName , cedarHandler )
107+ } else if ( handler ) {
108+ CEDAR_HANDLERS . set ( routeName , wrapLegacyHandler ( handler as LegacyHandler ) )
109+ }
110+
111+ if ( ! handler && ! cedarHandler ) {
63112 console . warn (
64113 routeName ,
65114 'at' ,
66115 fnPath ,
67- 'does not have a function called handler defined.' ,
116+ 'does not have a function called handler or handleRequest defined.' ,
68117 )
69118 }
119+
120+ cedarRouteManifest . push ( {
121+ path : routePath ,
122+ methods :
123+ routeName === 'graphql' ? [ 'GET' , 'POST' , 'OPTIONS' ] : [ 'GET' , 'POST' ] ,
124+ type :
125+ routeName === 'graphql'
126+ ? 'graphql'
127+ : routeName === 'health'
128+ ? 'health'
129+ : routeName . toLowerCase ( ) . includes ( 'auth' )
130+ ? 'auth'
131+ : 'function' ,
132+ entry : fnPath ,
133+ } )
134+
70135 console . log (
71136 terminalLink ( ansis . magenta ( '/' + routeName ) , 'file://' + fnPath ) ,
72137 ansis . dim . italic ( Date . now ( ) - ts + ' ms' ) ,
@@ -135,25 +200,75 @@ interface LambdaHandlerRequest extends RequestGenericInterface {
135200}
136201
137202/**
138- This will take a fastify request
139- Then convert it to a lambdaEvent, and pass it to the the appropriate handler for the routeName
140- The LAMBDA_FUNCTIONS lookup has been populated already by this point
141- **/
203+ * This will take a fastify request
204+ * Then convert it to a lambdaEvent, and pass it to the the appropriate handler
205+ * for the routeName
206+ * The CEDAR_HANDLERS and LAMBDA_FUNCTIONS maps have been populated already by
207+ * this point
208+ */
142209export const lambdaRequestHandler = async (
143210 req : FastifyRequest < LambdaHandlerRequest > ,
144211 reply : FastifyReply ,
145212) => {
146213 const { routeName } = req . params
214+ const cedarHandlerCandidate = CEDAR_HANDLERS . get ( routeName )
215+ const cedarHandler =
216+ typeof cedarHandlerCandidate === 'function'
217+ ? cedarHandlerCandidate
218+ : undefined
219+
220+ if ( cedarHandler ) {
221+ const requestBody =
222+ req . method === 'GET' || req . method === 'HEAD'
223+ ? undefined
224+ : typeof req . rawBody === 'string'
225+ ? req . rawBody
226+ : req . rawBody
227+ ? Buffer . from ( req . rawBody ) . toString ( )
228+ : undefined
229+
230+ const href = `${ req . protocol } ://${ req . hostname } ${ req . raw . url ?? '/' } `
231+ const request = new Request ( href , {
232+ method : req . method ,
233+ headers : req . headers as HeadersInit ,
234+ body : requestBody ,
235+ } )
236+
237+ const ctx = await buildCedarContext ( request , {
238+ params : {
239+ routeName,
240+ } ,
241+ } )
242+
243+ const response = await cedarHandler ( request , ctx )
244+
245+ reply . status ( response . status )
246+
247+ response . headers . forEach ( ( value : string , name : string ) => {
248+ reply . header ( name , value )
249+ } )
250+
251+ const body = await response . arrayBuffer ( )
252+ reply . send ( Buffer . from ( body ) )
253+
254+ return
255+ }
256+
257+ const handler = LAMBDA_FUNCTIONS . get ( routeName )
147258
148- if ( ! LAMBDA_FUNCTIONS [ routeName ] ) {
259+ if ( handler ) {
260+ return requestHandler ( req , reply , handler )
261+ } else {
149262 const errorMessage = `Function "${ routeName } " was not found.`
150263 req . log . error ( errorMessage )
151264 reply . status ( 404 )
152265
153266 if ( process . env . NODE_ENV === 'development' ) {
154267 const devError = {
155268 error : errorMessage ,
156- availableFunctions : Object . keys ( LAMBDA_FUNCTIONS ) ,
269+ availableFunctions : [
270+ ...new Set ( [ ...LAMBDA_FUNCTIONS . keys ( ) , ...CEDAR_HANDLERS . keys ( ) ] ) ,
271+ ] ,
157272 }
158273 reply . send ( devError )
159274 } else {
@@ -162,5 +277,4 @@ export const lambdaRequestHandler = async (
162277
163278 return
164279 }
165- return requestHandler ( req , reply , LAMBDA_FUNCTIONS [ routeName ] )
166280}
0 commit comments