From 2069e19ee96f35aeab7062bd05d5df155cd9da90 Mon Sep 17 00:00:00 2001 From: Paulo Date: Thu, 7 Mar 2024 11:54:23 +0100 Subject: [PATCH 1/3] feat: ratelimit config from source --- node/config.test.ts | 66 +++++++++++++++++++++++++++++++-- node/config.ts | 2 + node/manifest.test.ts | 86 +++++++++++++++++++++++++++++++++++++++++++ node/manifest.ts | 71 +++++++++++++++++++++++++++++++++-- node/ratelimit.ts | 30 +++++++++++++++ 5 files changed, 247 insertions(+), 8 deletions(-) create mode 100644 node/ratelimit.ts diff --git a/node/config.test.ts b/node/config.test.ts index b5cb1e71..ed7e4aee 100644 --- a/node/config.test.ts +++ b/node/config.test.ts @@ -13,6 +13,7 @@ import { bundle } from './bundler.js' import { FunctionConfig, getFunctionConfig } from './config.js' import type { Declaration } from './declaration.js' import { ImportMap } from './import_map.js' +import { RatelimitAction, RatelimitAggregator } from './ratelimit.js' const importMapFile = { baseURL: new URL('file:///some/path/import-map.json'), @@ -83,7 +84,7 @@ const functions: TestFunctions[] = [ }, { testName: 'config with wrong onError', - name: 'func7', + name: 'func6', source: ` export default async () => new Response("Hello from function two") export const config = { onError: "foo" } @@ -93,7 +94,7 @@ const functions: TestFunctions[] = [ { testName: 'config with `path`', expectedConfig: { path: '/home' }, - name: 'func6', + name: 'func7', source: ` export default async () => new Response("Hello from function three") @@ -108,17 +109,74 @@ const functions: TestFunctions[] = [ name: 'a displayName', onError: 'bypass', }, - name: 'func6', + name: 'func8', source: ` export default async () => new Response("Hello from function three") - export const config = { path: "/home", + export const config = { + path: "/home", generator: '@netlify/fake-plugin@1.0.0', name: 'a displayName', onError: 'bypass', } `, }, + { + testName: 'config with ratelimit', + expectedConfig: { + path: '/ratelimit', + name: 'a limit rate', + ratelimit: { + windowSize: 10, + windowLimit: 100, + aggregateBy: [RatelimitAggregator.IP, RatelimitAggregator.Domain], + }, + }, + name: 'func9', + source: ` + export default async () => new Response("Rate my limits") + + export const config = { + path: "/ratelimit", + ratelimit: { + windowSize: 10, + windowLimit: 100, + aggregateBy: ["ip", "domain"], + }, + name: 'a limit rate', + } + `, + }, + { + testName: 'config with rewrite', + expectedConfig: { + path: '/rewrite', + name: 'a limit rewrite', + ratelimit: { + action: RatelimitAction.Rewrite, + to: '/rewritten', + windowSize: 20, + windowLimit: 200, + aggregateBy: [RatelimitAggregator.IP, RatelimitAggregator.Domain], + }, + }, + name: 'func9', + source: ` + export default async () => new Response("Rate my limits") + + export const config = { + path: "/rewrite", + ratelimit: { + action: "rewrite", + to: "/rewritten", + windowSize: 20, + windowLimit: 200, + aggregateBy: ["ip", "domain"], + }, + name: 'a limit rewrite', + } + `, + }, ] describe('`getFunctionConfig` extracts configuration properties from function file', () => { test.each(functions)('$testName', async (func) => { diff --git a/node/config.ts b/node/config.ts index 3d4b8ab2..f8ef3976 100644 --- a/node/config.ts +++ b/node/config.ts @@ -10,6 +10,7 @@ import { EdgeFunction } from './edge_function.js' import { ImportMap } from './import_map.js' import { Logger } from './logger.js' import { getPackagePath } from './package_json.js' +import { Ratelimit } from './ratelimit.js' enum ConfigExitCode { Success = 0, @@ -46,6 +47,7 @@ export interface FunctionConfig { name?: string generator?: string method?: HTTPMethod | HTTPMethod[] + ratelimit?: Ratelimit } const getConfigExtractor = () => { diff --git a/node/manifest.test.ts b/node/manifest.test.ts index f138482f..a088f034 100644 --- a/node/manifest.test.ts +++ b/node/manifest.test.ts @@ -9,6 +9,7 @@ import { BundleError } from './bundle_error.js' import { Cache, FunctionConfig } from './config.js' import { Declaration } from './declaration.js' import { generateManifest } from './manifest.js' +import { RatelimitAction, RatelimitAggregator } from './ratelimit.js' test('Generates a manifest with different bundles', () => { const bundle1 = { @@ -486,3 +487,88 @@ test('Returns functions without a declaration and unrouted functions', () => { expect(declarationsWithoutFunction).toEqual(['func-3']) expect(unroutedFunctions).toEqual(['func-2', 'func-4']) }) + +test('Generates a manifest with rate limit config', () => { + const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }] + const declarations: Declaration[] = [{ function: 'func-1', path: '/f1/*' }] + + const userFunctionConfig: Record = { + 'func-1': { ratelimit: { windowLimit: 100, windowSize: 60 } }, + } + const { manifest } = generateManifest({ + bundles: [], + declarations, + functions, + userFunctionConfig, + }) + + const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }] + const expectedFunctionConfig = { + 'func-1': { + traffic_rules_config: { + action: { + type: 'rate_limit', + config: { + rate_limit_config: { + window_limit: 100, + window_size: 60, + algorithm: 'sliding_window', + }, + aggregate: { + keys: [{ type: 'domain' }], + }, + }, + }, + }, + }, + } + expect(manifest.routes).toEqual(expectedRoutes) + expect(manifest.function_config).toEqual(expectedFunctionConfig) +}) + +test('Generates a manifest with rewrite config', () => { + const functions = [{ name: 'func-1', path: '/path/to/func-1.ts' }] + const declarations: Declaration[] = [{ function: 'func-1', path: '/f1/*' }] + + const userFunctionConfig: Record = { + 'func-1': { + ratelimit: { + action: RatelimitAction.Rewrite, + to: '/new_path', + windowLimit: 100, + windowSize: 60, + aggregateBy: [RatelimitAggregator.Domain, RatelimitAggregator.IP], + }, + }, + } + const { manifest } = generateManifest({ + bundles: [], + declarations, + functions, + userFunctionConfig, + }) + + const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }] + const expectedFunctionConfig = { + 'func-1': { + traffic_rules_config: { + action: { + type: 'rewrite', + config: { + to: '/new_path', + rate_limit_config: { + window_limit: 100, + window_size: 60, + algorithm: 'sliding_window', + }, + aggregate: { + keys: [{ type: 'domain' }, { type: 'ip' }], + }, + }, + }, + }, + }, + } + expect(manifest.routes).toEqual(expectedRoutes) + expect(manifest.function_config).toEqual(expectedFunctionConfig) +}) diff --git a/node/manifest.ts b/node/manifest.ts index 1d28c57a..663ae2ab 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -9,6 +9,13 @@ import { EdgeFunction } from './edge_function.js' import { FeatureFlags } from './feature_flags.js' import { Layer } from './layer.js' import { getPackageVersion } from './package_json.js' +import { + Ratelimit, + RewriteActionConfig, + RatelimitAction, + RatelimitAlgorithm, + RatelimitAggregator, +} from './ratelimit.js' import { nonNullable } from './utils/non_nullable.js' import { ExtendedURLPattern } from './utils/urlpattern.js' @@ -20,12 +27,33 @@ interface Route { methods?: string[] } +interface TrafficRulesConfig { + action: { + type: string + config: { + rate_limit_config: { + algorithm: string + window_size: number + window_limit: number + } + aggregate: { + keys: { + type: string + }[] + } + to?: string + } + } +} + export interface EdgeFunctionConfig { excluded_patterns: string[] on_error?: string generator?: string name?: string + traffic_rules_config?: TrafficRulesConfig } + interface Manifest { bundler_version: string bundles: { asset: string; format: string }[] @@ -122,7 +150,7 @@ const generateManifest = ({ const routedFunctions = new Set() const declarationsWithoutFunction = new Set() - for (const [name, { excludedPath, onError }] of Object.entries(userFunctionConfig)) { + for (const [name, { excludedPath, onError, ratelimit }] of Object.entries(userFunctionConfig)) { // If the config block is for a function that is not defined, discard it. if (manifestFunctionConfig[name] === undefined) { continue @@ -130,10 +158,14 @@ const generateManifest = ({ addExcludedPatterns(name, manifestFunctionConfig, excludedPath) - manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError } + manifestFunctionConfig[name] = { + ...manifestFunctionConfig[name], + on_error: onError, + traffic_rules_config: getTrafficRulesConfig(ratelimit), + } } - for (const [name, { excludedPath, path, onError, ...rest }] of Object.entries(internalFunctionConfig)) { + for (const [name, { excludedPath, path, onError, ratelimit, ...rest }] of Object.entries(internalFunctionConfig)) { // If the config block is for a function that is not defined, discard it. if (manifestFunctionConfig[name] === undefined) { continue @@ -141,7 +173,12 @@ const generateManifest = ({ addExcludedPatterns(name, manifestFunctionConfig, excludedPath) - manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, ...rest } + manifestFunctionConfig[name] = { + ...manifestFunctionConfig[name], + on_error: onError, + traffic_rules_config: getTrafficRulesConfig(ratelimit), + ...rest, + } } declarations.forEach((declaration) => { @@ -202,6 +239,32 @@ const generateManifest = ({ return { declarationsWithoutFunction: [...declarationsWithoutFunction], manifest, unroutedFunctions } } +const getTrafficRulesConfig = (rl: Ratelimit | undefined) => { + if (rl === undefined) { + return + } + + const ratelimitAgg = Array.isArray(rl.aggregateBy) ? rl.aggregateBy : [RatelimitAggregator.Domain] + const rewriteConfig = (rl as RewriteActionConfig).to ? { to: (rl as RewriteActionConfig).to } : undefined + + return { + action: { + type: rl.action || RatelimitAction.Limit, + config: { + ...rewriteConfig, + rate_limit_config: { + window_limit: rl.windowLimit, + window_size: rl.windowSize, + algorithm: RatelimitAlgorithm.SlidingWindow, + }, + aggregate: { + keys: ratelimitAgg.map((agg) => ({ type: agg })), + }, + }, + }, + } +} + const pathToRegularExpression = (path: string) => { if (!path) { return null diff --git a/node/ratelimit.ts b/node/ratelimit.ts new file mode 100644 index 00000000..7a1ae507 --- /dev/null +++ b/node/ratelimit.ts @@ -0,0 +1,30 @@ +export enum RatelimitAlgorithm { + SlidingWindow = 'sliding_window', +} + +export enum RatelimitAggregator { + Domain = 'domain', + IP = 'ip', +} + +export enum RatelimitAction { + Limit = 'rate_limit', + Rewrite = 'rewrite', +} + +interface SlidingWindow { + windowLimit: number + windowSize: number +} + +export type RewriteActionConfig = SlidingWindow & { + to: string +} + +interface RatelimitConfig { + action?: RatelimitAction + aggregateBy?: RatelimitAggregator | RatelimitAggregator[] + algorithm?: RatelimitAlgorithm +} + +export type Ratelimit = RatelimitConfig & (SlidingWindow | RewriteActionConfig) From 89799640d1db9b918248e4ea8ad1e67d3a489669 Mon Sep 17 00:00:00 2001 From: Paulo Date: Mon, 11 Mar 2024 19:45:33 +0100 Subject: [PATCH 2/3] feat: config consistent with lambda --- node/config.test.ts | 8 ++++---- node/config.ts | 2 +- node/manifest.test.ts | 8 ++++---- node/manifest.ts | 12 ++++++------ 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/node/config.test.ts b/node/config.test.ts index ed7e4aee..da487dd4 100644 --- a/node/config.test.ts +++ b/node/config.test.ts @@ -126,7 +126,7 @@ const functions: TestFunctions[] = [ expectedConfig: { path: '/ratelimit', name: 'a limit rate', - ratelimit: { + rateLimit: { windowSize: 10, windowLimit: 100, aggregateBy: [RatelimitAggregator.IP, RatelimitAggregator.Domain], @@ -138,7 +138,7 @@ const functions: TestFunctions[] = [ export const config = { path: "/ratelimit", - ratelimit: { + rateLimit: { windowSize: 10, windowLimit: 100, aggregateBy: ["ip", "domain"], @@ -152,7 +152,7 @@ const functions: TestFunctions[] = [ expectedConfig: { path: '/rewrite', name: 'a limit rewrite', - ratelimit: { + rateLimit: { action: RatelimitAction.Rewrite, to: '/rewritten', windowSize: 20, @@ -166,7 +166,7 @@ const functions: TestFunctions[] = [ export const config = { path: "/rewrite", - ratelimit: { + rateLimit: { action: "rewrite", to: "/rewritten", windowSize: 20, diff --git a/node/config.ts b/node/config.ts index f8ef3976..280b55ce 100644 --- a/node/config.ts +++ b/node/config.ts @@ -47,7 +47,7 @@ export interface FunctionConfig { name?: string generator?: string method?: HTTPMethod | HTTPMethod[] - ratelimit?: Ratelimit + rateLimit?: Ratelimit } const getConfigExtractor = () => { diff --git a/node/manifest.test.ts b/node/manifest.test.ts index a088f034..fa320d66 100644 --- a/node/manifest.test.ts +++ b/node/manifest.test.ts @@ -493,7 +493,7 @@ test('Generates a manifest with rate limit config', () => { const declarations: Declaration[] = [{ function: 'func-1', path: '/f1/*' }] const userFunctionConfig: Record = { - 'func-1': { ratelimit: { windowLimit: 100, windowSize: 60 } }, + 'func-1': { rateLimit: { windowLimit: 100, windowSize: 60 } }, } const { manifest } = generateManifest({ bundles: [], @@ -505,7 +505,7 @@ test('Generates a manifest with rate limit config', () => { const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }] const expectedFunctionConfig = { 'func-1': { - traffic_rules_config: { + traffic_rules: { action: { type: 'rate_limit', config: { @@ -532,7 +532,7 @@ test('Generates a manifest with rewrite config', () => { const userFunctionConfig: Record = { 'func-1': { - ratelimit: { + rateLimit: { action: RatelimitAction.Rewrite, to: '/new_path', windowLimit: 100, @@ -551,7 +551,7 @@ test('Generates a manifest with rewrite config', () => { const expectedRoutes = [{ function: 'func-1', pattern: '^/f1(?:/(.*))/?$', excluded_patterns: [], path: '/f1/*' }] const expectedFunctionConfig = { 'func-1': { - traffic_rules_config: { + traffic_rules: { action: { type: 'rewrite', config: { diff --git a/node/manifest.ts b/node/manifest.ts index 663ae2ab..45cfb377 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -27,7 +27,7 @@ interface Route { methods?: string[] } -interface TrafficRulesConfig { +interface TrafficRules { action: { type: string config: { @@ -51,7 +51,7 @@ export interface EdgeFunctionConfig { on_error?: string generator?: string name?: string - traffic_rules_config?: TrafficRulesConfig + traffic_rules?: TrafficRules } interface Manifest { @@ -150,7 +150,7 @@ const generateManifest = ({ const routedFunctions = new Set() const declarationsWithoutFunction = new Set() - for (const [name, { excludedPath, onError, ratelimit }] of Object.entries(userFunctionConfig)) { + for (const [name, { excludedPath, onError, rateLimit }] of Object.entries(userFunctionConfig)) { // If the config block is for a function that is not defined, discard it. if (manifestFunctionConfig[name] === undefined) { continue @@ -161,11 +161,11 @@ const generateManifest = ({ manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, - traffic_rules_config: getTrafficRulesConfig(ratelimit), + traffic_rules: getTrafficRulesConfig(rateLimit), } } - for (const [name, { excludedPath, path, onError, ratelimit, ...rest }] of Object.entries(internalFunctionConfig)) { + for (const [name, { excludedPath, path, onError, rateLimit, ...rest }] of Object.entries(internalFunctionConfig)) { // If the config block is for a function that is not defined, discard it. if (manifestFunctionConfig[name] === undefined) { continue @@ -176,7 +176,7 @@ const generateManifest = ({ manifestFunctionConfig[name] = { ...manifestFunctionConfig[name], on_error: onError, - traffic_rules_config: getTrafficRulesConfig(ratelimit), + traffic_rules: getTrafficRulesConfig(rateLimit), ...rest, } } From 6f9c637a36240073ac26c13d005f24ccfa7991cf Mon Sep 17 00:00:00 2001 From: Paulo Date: Mon, 11 Mar 2024 19:53:10 +0100 Subject: [PATCH 3/3] chore: ratelimit -> rate_limit --- node/config.test.ts | 8 ++++---- node/config.ts | 4 ++-- node/manifest.test.ts | 6 +++--- node/manifest.ts | 20 +++++++------------- node/rate_limit.ts | 30 ++++++++++++++++++++++++++++++ node/ratelimit.ts | 30 ------------------------------ 6 files changed, 46 insertions(+), 52 deletions(-) create mode 100644 node/rate_limit.ts delete mode 100644 node/ratelimit.ts diff --git a/node/config.test.ts b/node/config.test.ts index da487dd4..cc9b4c56 100644 --- a/node/config.test.ts +++ b/node/config.test.ts @@ -13,7 +13,7 @@ import { bundle } from './bundler.js' import { FunctionConfig, getFunctionConfig } from './config.js' import type { Declaration } from './declaration.js' import { ImportMap } from './import_map.js' -import { RatelimitAction, RatelimitAggregator } from './ratelimit.js' +import { RateLimitAction, RateLimitAggregator } from './rate_limit.js' const importMapFile = { baseURL: new URL('file:///some/path/import-map.json'), @@ -129,7 +129,7 @@ const functions: TestFunctions[] = [ rateLimit: { windowSize: 10, windowLimit: 100, - aggregateBy: [RatelimitAggregator.IP, RatelimitAggregator.Domain], + aggregateBy: [RateLimitAggregator.IP, RateLimitAggregator.Domain], }, }, name: 'func9', @@ -153,11 +153,11 @@ const functions: TestFunctions[] = [ path: '/rewrite', name: 'a limit rewrite', rateLimit: { - action: RatelimitAction.Rewrite, + action: RateLimitAction.Rewrite, to: '/rewritten', windowSize: 20, windowLimit: 200, - aggregateBy: [RatelimitAggregator.IP, RatelimitAggregator.Domain], + aggregateBy: [RateLimitAggregator.IP, RateLimitAggregator.Domain], }, }, name: 'func9', diff --git a/node/config.ts b/node/config.ts index 280b55ce..2e0b605d 100644 --- a/node/config.ts +++ b/node/config.ts @@ -10,7 +10,7 @@ import { EdgeFunction } from './edge_function.js' import { ImportMap } from './import_map.js' import { Logger } from './logger.js' import { getPackagePath } from './package_json.js' -import { Ratelimit } from './ratelimit.js' +import { RateLimit } from './rate_limit.js' enum ConfigExitCode { Success = 0, @@ -47,7 +47,7 @@ export interface FunctionConfig { name?: string generator?: string method?: HTTPMethod | HTTPMethod[] - rateLimit?: Ratelimit + rateLimit?: RateLimit } const getConfigExtractor = () => { diff --git a/node/manifest.test.ts b/node/manifest.test.ts index fa320d66..d05f0a4a 100644 --- a/node/manifest.test.ts +++ b/node/manifest.test.ts @@ -9,7 +9,7 @@ import { BundleError } from './bundle_error.js' import { Cache, FunctionConfig } from './config.js' import { Declaration } from './declaration.js' import { generateManifest } from './manifest.js' -import { RatelimitAction, RatelimitAggregator } from './ratelimit.js' +import { RateLimitAction, RateLimitAggregator } from './rate_limit.js' test('Generates a manifest with different bundles', () => { const bundle1 = { @@ -533,11 +533,11 @@ test('Generates a manifest with rewrite config', () => { const userFunctionConfig: Record = { 'func-1': { rateLimit: { - action: RatelimitAction.Rewrite, + action: RateLimitAction.Rewrite, to: '/new_path', windowLimit: 100, windowSize: 60, - aggregateBy: [RatelimitAggregator.Domain, RatelimitAggregator.IP], + aggregateBy: [RateLimitAggregator.Domain, RateLimitAggregator.IP], }, }, } diff --git a/node/manifest.ts b/node/manifest.ts index 45cfb377..13b2908c 100644 --- a/node/manifest.ts +++ b/node/manifest.ts @@ -9,13 +9,7 @@ import { EdgeFunction } from './edge_function.js' import { FeatureFlags } from './feature_flags.js' import { Layer } from './layer.js' import { getPackageVersion } from './package_json.js' -import { - Ratelimit, - RewriteActionConfig, - RatelimitAction, - RatelimitAlgorithm, - RatelimitAggregator, -} from './ratelimit.js' +import { RateLimit, RateLimitAction, RateLimitAlgorithm, RateLimitAggregator } from './rate_limit.js' import { nonNullable } from './utils/non_nullable.js' import { ExtendedURLPattern } from './utils/urlpattern.js' @@ -239,26 +233,26 @@ const generateManifest = ({ return { declarationsWithoutFunction: [...declarationsWithoutFunction], manifest, unroutedFunctions } } -const getTrafficRulesConfig = (rl: Ratelimit | undefined) => { +const getTrafficRulesConfig = (rl: RateLimit | undefined) => { if (rl === undefined) { return } - const ratelimitAgg = Array.isArray(rl.aggregateBy) ? rl.aggregateBy : [RatelimitAggregator.Domain] - const rewriteConfig = (rl as RewriteActionConfig).to ? { to: (rl as RewriteActionConfig).to } : undefined + const rateLimitAgg = Array.isArray(rl.aggregateBy) ? rl.aggregateBy : [RateLimitAggregator.Domain] + const rewriteConfig = 'to' in rl && typeof rl.to === 'string' ? { to: rl.to } : undefined return { action: { - type: rl.action || RatelimitAction.Limit, + type: rl.action || RateLimitAction.Limit, config: { ...rewriteConfig, rate_limit_config: { window_limit: rl.windowLimit, window_size: rl.windowSize, - algorithm: RatelimitAlgorithm.SlidingWindow, + algorithm: RateLimitAlgorithm.SlidingWindow, }, aggregate: { - keys: ratelimitAgg.map((agg) => ({ type: agg })), + keys: rateLimitAgg.map((agg) => ({ type: agg })), }, }, }, diff --git a/node/rate_limit.ts b/node/rate_limit.ts new file mode 100644 index 00000000..3e1ca721 --- /dev/null +++ b/node/rate_limit.ts @@ -0,0 +1,30 @@ +export enum RateLimitAlgorithm { + SlidingWindow = 'sliding_window', +} + +export enum RateLimitAggregator { + Domain = 'domain', + IP = 'ip', +} + +export enum RateLimitAction { + Limit = 'rate_limit', + Rewrite = 'rewrite', +} + +interface SlidingWindow { + windowLimit: number + windowSize: number +} + +export type RewriteActionConfig = SlidingWindow & { + to: string +} + +interface RateLimitConfig { + action?: RateLimitAction + aggregateBy?: RateLimitAggregator | RateLimitAggregator[] + algorithm?: RateLimitAlgorithm +} + +export type RateLimit = RateLimitConfig & (SlidingWindow | RewriteActionConfig) diff --git a/node/ratelimit.ts b/node/ratelimit.ts deleted file mode 100644 index 7a1ae507..00000000 --- a/node/ratelimit.ts +++ /dev/null @@ -1,30 +0,0 @@ -export enum RatelimitAlgorithm { - SlidingWindow = 'sliding_window', -} - -export enum RatelimitAggregator { - Domain = 'domain', - IP = 'ip', -} - -export enum RatelimitAction { - Limit = 'rate_limit', - Rewrite = 'rewrite', -} - -interface SlidingWindow { - windowLimit: number - windowSize: number -} - -export type RewriteActionConfig = SlidingWindow & { - to: string -} - -interface RatelimitConfig { - action?: RatelimitAction - aggregateBy?: RatelimitAggregator | RatelimitAggregator[] - algorithm?: RatelimitAlgorithm -} - -export type Ratelimit = RatelimitConfig & (SlidingWindow | RewriteActionConfig)