lambder 3.3.1 → 3.3.3

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.
@@ -0,0 +1,50 @@
1
+ import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2
+ export interface LambderRateLimitPolicy {
3
+ perMin?: number;
4
+ per10Min?: number;
5
+ perHour?: number;
6
+ perDay?: number;
7
+ perWeek?: number;
8
+ perMonth?: number;
9
+ }
10
+ export type LambderRateLimitExceededMap = Partial<Record<keyof LambderRateLimitPolicy, number>>;
11
+ /** `false` when allowed, otherwise the window(s) whose limit was hit. */
12
+ export type LambderRateLimitResult = false | LambderRateLimitExceededMap;
13
+ export interface LambderDdbRateLimiterOptions {
14
+ tableName: string;
15
+ region?: string;
16
+ /** Partition key prefix, keeps counters separated from other items. */
17
+ keyPrefix?: string;
18
+ /** Multiplier applied to the window length when setting the item TTL. */
19
+ ttlWindowMultiplier?: number;
20
+ /** Allow the request when DynamoDB itself errors. Defaults to false. */
21
+ failOpen?: boolean;
22
+ client?: DynamoDBClient;
23
+ }
24
+ /**
25
+ * Fixed-window rate limiter backed by DynamoDB.
26
+ *
27
+ * Each window is a single item counted with a conditional `ADD`, so the
28
+ * increment and the limit check happen atomically in one request. Windows are
29
+ * evaluated from smallest to largest and evaluation stops at the first
30
+ * exceeded window, which keeps blocked requests cheap and avoids inflating the
31
+ * larger counters. Items carry an `expiresAt` attribute for DynamoDB TTL.
32
+ *
33
+ * Table shape: string hash key `pk`, string range key `sk`, TTL on `expiresAt`.
34
+ */
35
+ export declare class LambderDdbRateLimiter {
36
+ readonly tableName: string;
37
+ readonly keyPrefix: string;
38
+ private readonly client;
39
+ private readonly ttlWindowMultiplier;
40
+ private readonly failOpen;
41
+ constructor(options: LambderDdbRateLimiterOptions);
42
+ /**
43
+ * Increment every configured window for `trackerKey` (IP, session, user id, ...)
44
+ * and report whether any of them is over its limit.
45
+ */
46
+ isRateLimited(trackerKey: string, policy: LambderRateLimitPolicy): Promise<LambderRateLimitResult>;
47
+ /** Increments one window counter. Returns true when the limit was already reached. */
48
+ private incrementWindow;
49
+ }
50
+ export default LambderDdbRateLimiter;
@@ -0,0 +1,87 @@
1
+ import { DynamoDBClient, UpdateItemCommand, } from "@aws-sdk/client-dynamodb";
2
+ const WINDOW_CONFIG = [
3
+ { key: "perMin", seconds: 60 },
4
+ { key: "per10Min", seconds: 10 * 60 },
5
+ { key: "perHour", seconds: 60 * 60 },
6
+ { key: "perDay", seconds: 24 * 60 * 60 },
7
+ { key: "perWeek", seconds: 7 * 24 * 60 * 60 },
8
+ { key: "perMonth", seconds: 30 * 24 * 60 * 60 },
9
+ ];
10
+ /**
11
+ * Fixed-window rate limiter backed by DynamoDB.
12
+ *
13
+ * Each window is a single item counted with a conditional `ADD`, so the
14
+ * increment and the limit check happen atomically in one request. Windows are
15
+ * evaluated from smallest to largest and evaluation stops at the first
16
+ * exceeded window, which keeps blocked requests cheap and avoids inflating the
17
+ * larger counters. Items carry an `expiresAt` attribute for DynamoDB TTL.
18
+ *
19
+ * Table shape: string hash key `pk`, string range key `sk`, TTL on `expiresAt`.
20
+ */
21
+ export class LambderDdbRateLimiter {
22
+ tableName;
23
+ keyPrefix;
24
+ client;
25
+ ttlWindowMultiplier;
26
+ failOpen;
27
+ constructor(options) {
28
+ if (!options.tableName.trim())
29
+ throw new Error("tableName is required");
30
+ this.tableName = options.tableName;
31
+ this.keyPrefix = options.keyPrefix ?? "RL";
32
+ this.ttlWindowMultiplier = options.ttlWindowMultiplier ?? 2;
33
+ if (!Number.isFinite(this.ttlWindowMultiplier) || this.ttlWindowMultiplier < 1) {
34
+ throw new Error("ttlWindowMultiplier must be a number greater than or equal to 1");
35
+ }
36
+ this.failOpen = options.failOpen ?? false;
37
+ this.client = options.client ?? new DynamoDBClient(options.region ? { region: options.region } : {});
38
+ }
39
+ /**
40
+ * Increment every configured window for `trackerKey` (IP, session, user id, ...)
41
+ * and report whether any of them is over its limit.
42
+ */
43
+ async isRateLimited(trackerKey, policy) {
44
+ for (const { key, seconds } of WINDOW_CONFIG) {
45
+ const limit = policy[key];
46
+ if (!limit)
47
+ continue;
48
+ const exceeded = await this.incrementWindow(trackerKey, key, seconds, limit);
49
+ if (exceeded)
50
+ return { [key]: limit };
51
+ }
52
+ return false;
53
+ }
54
+ /** Increments one window counter. Returns true when the limit was already reached. */
55
+ async incrementWindow(trackerKey, sortKeyPrefix, windowSeconds, limit) {
56
+ const nowSeconds = Math.floor(Date.now() / 1000);
57
+ const windowStart = Math.floor(nowSeconds / windowSeconds) * windowSeconds;
58
+ const expiresAt = nowSeconds + Math.ceil(windowSeconds * this.ttlWindowMultiplier);
59
+ const input = {
60
+ TableName: this.tableName,
61
+ Key: {
62
+ pk: { S: `${this.keyPrefix}#${trackerKey}` },
63
+ sk: { S: `${sortKeyPrefix}#${windowStart}` },
64
+ },
65
+ UpdateExpression: "ADD #count :one SET #expiresAt = if_not_exists(#expiresAt, :expiresAt)",
66
+ ConditionExpression: "attribute_not_exists(#count) OR #count < :limit",
67
+ ExpressionAttributeNames: { "#count": "count", "#expiresAt": "expiresAt" },
68
+ ExpressionAttributeValues: {
69
+ ":one": { N: "1" },
70
+ ":expiresAt": { N: String(expiresAt) },
71
+ ":limit": { N: String(limit) },
72
+ },
73
+ };
74
+ try {
75
+ await this.client.send(new UpdateItemCommand(input));
76
+ return false;
77
+ }
78
+ catch (error) {
79
+ if (error.name === "ConditionalCheckFailedException")
80
+ return true;
81
+ if (this.failOpen)
82
+ return false;
83
+ throw error;
84
+ }
85
+ }
86
+ }
87
+ export default LambderDdbRateLimiter;
package/dist/index.d.ts CHANGED
@@ -19,6 +19,8 @@ export type { LambderSessionCookieOptions } from "./LambderSessionController.js"
19
19
  export type { LambderSessionContext } from "./LambderSessionManager.js";
20
20
  export { LambderDdbCache } from "./LambderDdbCache.js";
21
21
  export type { LambderDdbCacheOptions, LambderDdbCacheSetOptions, LambderDdbCacheGetOrSetOptions, } from "./LambderDdbCache.js";
22
+ export { LambderDdbRateLimiter } from "./LambderDdbRateLimiter.js";
23
+ export type { LambderDdbRateLimiterOptions, LambderRateLimitPolicy, LambderRateLimitExceededMap, LambderRateLimitResult, } from "./LambderDdbRateLimiter.js";
22
24
  export { createLambderI18n } from "./LambderI18n.js";
23
25
  export type { LambderLanguageMeta, LambderI18nConfig, LambderI18nInstance, LambderI18nTranslator, LambderI18nExtractParams, LambderI18nCodes, LambderI18nKeys, LambderI18nTranslatorFor, } from "./LambderI18n.js";
24
26
  export { type ApiContractShape, } from "./LambderApiContract.js";
package/dist/index.js CHANGED
@@ -16,6 +16,8 @@ export { LambderTemplatingEngine } from "./LambderTemplatingEngine.js";
16
16
  export { LambderPublicFilesHandler } from "./LambderPublicFiles.js";
17
17
  // DynamoDB-backed compressed cache (standalone, server-only)
18
18
  export { LambderDdbCache } from "./LambderDdbCache.js";
19
+ // DynamoDB-backed fixed-window rate limiter (standalone, server-only)
20
+ export { LambderDdbRateLimiter } from "./LambderDdbRateLimiter.js";
19
21
  // Typed translations (standalone, isomorphic)
20
22
  export { createLambderI18n } from "./LambderI18n.js";
21
23
  export { createContext, isV2HttpEvent } from "./LambderContext.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lambder",
3
- "version": "3.3.1",
3
+ "version": "3.3.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",