11import type { ArgumentPlaceholder , Expression , SpreadElement , JSXNamespacedName } from '@babel/types'
22
33import { InvocationMode , INVOCATION_MODE } from '../../../function.js'
4+ import { TrafficRules } from '../../../manifest.js'
5+ import { RateLimitAction , RateLimitAggregator , RateLimitAlgorithm } from '../../../rate_limit.js'
46import { FunctionBundlingUserError } from '../../../utils/error.js'
57import { nonNullable } from '../../../utils/non_nullable.js'
68import { getRoutes , Route } from '../../../utils/routes.js'
@@ -20,6 +22,7 @@ export type ISCValues = {
2022 routes ?: Route [ ]
2123 schedule ?: string
2224 methods ?: string [ ]
25+ trafficRules ?: TrafficRules
2326}
2427
2528export interface StaticAnalysisResult extends ISCValues {
@@ -71,6 +74,60 @@ const normalizeMethods = (input: unknown, name: string): string[] | undefined =>
7174 } )
7275}
7376
77+ /**
78+ * Extracts the `ratelimit` configuration from the exported config.
79+ */
80+ const getTrafficRulesConfig = ( input : unknown , name : string ) : TrafficRules | undefined => {
81+ if ( typeof input !== 'object' || input === null ) {
82+ throw new FunctionBundlingUserError (
83+ `Could not parse ratelimit declaration of function '${ name } '. Expecting an object, got ${ input } ` ,
84+ {
85+ functionName : name ,
86+ runtime : RUNTIME . JAVASCRIPT ,
87+ bundler : NODE_BUNDLER . ESBUILD ,
88+ } ,
89+ )
90+ }
91+
92+ const { windowSize, windowLimit, algorithm, aggregateBy, action } = input as Record < string , unknown >
93+
94+ if (
95+ typeof windowSize !== 'number' ||
96+ typeof windowLimit !== 'number' ||
97+ ! Number . isInteger ( windowSize ) ||
98+ ! Number . isInteger ( windowLimit )
99+ ) {
100+ throw new FunctionBundlingUserError (
101+ `Could not parse ratelimit declaration of function '${ name } '. Expecting 'windowSize' and 'limitSize' integer properties, got ${ input } ` ,
102+ {
103+ functionName : name ,
104+ runtime : RUNTIME . JAVASCRIPT ,
105+ bundler : NODE_BUNDLER . ESBUILD ,
106+ } ,
107+ )
108+ }
109+
110+ const rateLimitAgg = Array . isArray ( aggregateBy ) ? aggregateBy : [ RateLimitAggregator . Domain ]
111+ const rewriteConfig = 'to' in input && typeof input . to === 'string' ? { to : input . to } : undefined
112+
113+ return {
114+ action : {
115+ type : ( action as RateLimitAction ) || RateLimitAction . Limit ,
116+ config : {
117+ ...rewriteConfig ,
118+ rateLimitConfig : {
119+ windowLimit,
120+ windowSize,
121+ algorithm : ( algorithm as RateLimitAlgorithm ) || RateLimitAlgorithm . SlidingWindow ,
122+ } ,
123+ aggregate : {
124+ keys : rateLimitAgg . map ( ( agg ) => ( { type : agg } ) ) ,
125+ } ,
126+ } ,
127+ } ,
128+ }
129+ }
130+
74131/**
75132 * Loads a file at a given path, parses it into an AST, and returns a series of
76133 * data points, such as in-source configuration properties and other metadata.
@@ -131,6 +188,10 @@ export const parseSource = (source: string, { functionName }: FindISCDeclaration
131188 preferStatic : configExport . preferStatic === true ,
132189 } )
133190
191+ if ( configExport . rateLimit !== undefined ) {
192+ result . trafficRules = getTrafficRulesConfig ( configExport . rateLimit , functionName )
193+ }
194+
134195 return result
135196 }
136197
0 commit comments