fluxguard 0.1.1

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,125 @@
1
+ import { RedisOptions, Redis } from 'ioredis';
2
+ import { Request, Response, RequestHandler } from 'express';
3
+ import { Registry, Counter } from 'prom-client';
4
+
5
+ declare enum Algorithm {
6
+ FIXED_WINDOW = "FIXED_WINDOW",
7
+ SLIDING_WINDOW_LOG = "SLIDING_WINDOW_LOG",
8
+ SLIDING_WINDOW_COUNTER = "SLIDING_WINDOW_COUNTER",
9
+ TOKEN_BUCKET = "TOKEN_BUCKET"
10
+ }
11
+ type RateLimitResult = {
12
+ allowed: boolean;
13
+ limit: number;
14
+ remaining: number;
15
+ /** Epoch ms when the current window resets or limit fully replenishes (best effort). */
16
+ resetMs: number;
17
+ /** Suggested retry-after in ms when throttled. */
18
+ retryAfterMs?: number;
19
+ algorithm: Algorithm;
20
+ };
21
+ type FluxGuardMetricsSnapshot = {
22
+ totalChecks: number;
23
+ allowed: number;
24
+ throttled: number;
25
+ redisErrors: number;
26
+ };
27
+ type FluxGuardEvents = {
28
+ onAllowed?: (key: string, result: RateLimitResult) => void;
29
+ onThrottled?: (key: string, result: RateLimitResult) => void;
30
+ onRedisError?: (err: unknown) => void;
31
+ };
32
+ type FluxGuardConfig = {
33
+ algorithm: Algorithm;
34
+ /** Max requests (or burst capacity for token bucket). */
35
+ limit: number;
36
+ windowMs: number;
37
+ /** Redis connection — omit for in-memory limiting. */
38
+ redis?: RedisOptions | Redis;
39
+ keyPrefix?: string;
40
+ /** Injected clock — default `Date.now`. */
41
+ nowFn?: () => number;
42
+ failOpen?: boolean;
43
+ /** Optional external metrics hook — receives snapshot after each successful check path. */
44
+ metrics?: {
45
+ record?: (s: FluxGuardMetricsSnapshot) => void;
46
+ };
47
+ events?: FluxGuardEvents;
48
+ };
49
+
50
+ declare class FluxGuard {
51
+ private readonly cfg;
52
+ private readonly store;
53
+ private readonly prefix;
54
+ private readonly nowFn;
55
+ private readonly config;
56
+ private metrics;
57
+ constructor(cfg: FluxGuardConfig);
58
+ getMetrics(): FluxGuardMetricsSnapshot;
59
+ /** Merge event recording for external metrics (e.g. Prometheus). */
60
+ onMetricsRecord(fn: (snapshot: FluxGuardMetricsSnapshot) => void): void;
61
+ check(key: string): Promise<RateLimitResult>;
62
+ private checkLocal;
63
+ }
64
+
65
+ /**
66
+ * Storage abstraction: local Map or Redis (Lua via evalScript).
67
+ */
68
+ interface Store {
69
+ get(key: string): Promise<string | null>;
70
+ set(key: string, value: string, ttlMs: number): Promise<void>;
71
+ del(key: string): Promise<void>;
72
+ /**
73
+ * Execute Lua atomically (Redis). Local store may throw if used.
74
+ */
75
+ evalScript(script: string, keys: string[], args: (string | number)[]): Promise<unknown[]>;
76
+ }
77
+
78
+ declare class LocalStore implements Store {
79
+ private readonly clock;
80
+ private readonly data;
81
+ constructor(clock?: () => number);
82
+ get(key: string): Promise<string | null>;
83
+ set(key: string, value: string, ttlMs: number): Promise<void>;
84
+ del(key: string): Promise<void>;
85
+ evalScript(): Promise<unknown[]>;
86
+ /** Test helper: clear all keys */
87
+ _clear(): void;
88
+ }
89
+
90
+ declare class RedisStore implements Store {
91
+ private readonly redis;
92
+ private readonly owns;
93
+ private readonly shaCache;
94
+ constructor(redis: Redis | RedisOptions, optionsIsObject: boolean);
95
+ loadScriptFile(filename: string): string;
96
+ disconnect(): void;
97
+ get redisClient(): Redis;
98
+ get(key: string): Promise<string | null>;
99
+ set(key: string, value: string, ttlMs: number): Promise<void>;
100
+ del(key: string): Promise<void>;
101
+ evalScript(script: string, keys: string[], args: (string | number)[]): Promise<unknown[]>;
102
+ }
103
+
104
+ type ExpressLimiterOptions = {
105
+ keyExtract?: (req: Request) => string;
106
+ onThrottled?: (req: Request, res: Response, result: RateLimitResult) => void;
107
+ skipSuccessfulRequests?: boolean;
108
+ skipFailedRequests?: boolean;
109
+ headers?: boolean;
110
+ };
111
+ declare function fluxGuardMiddleware(limiter: FluxGuard, options?: ExpressLimiterOptions): RequestHandler;
112
+
113
+ type PrometheusMetrics = {
114
+ registry: Registry;
115
+ checksTotal: Counter<string>;
116
+ allowedTotal: Counter<string>;
117
+ throttledTotal: Counter<string>;
118
+ redisErrorsTotal: Counter<string>;
119
+ };
120
+ declare function createFluxGuardPrometheusMetrics(limiter: FluxGuard, options?: {
121
+ prefix?: string;
122
+ collectDefault?: boolean;
123
+ }): PrometheusMetrics;
124
+
125
+ export { Algorithm, type ExpressLimiterOptions, FluxGuard, type FluxGuardConfig, LocalStore, type PrometheusMetrics, type RateLimitResult, RedisStore, type Store, createFluxGuardPrometheusMetrics, fluxGuardMiddleware };
@@ -0,0 +1,125 @@
1
+ import { RedisOptions, Redis } from 'ioredis';
2
+ import { Request, Response, RequestHandler } from 'express';
3
+ import { Registry, Counter } from 'prom-client';
4
+
5
+ declare enum Algorithm {
6
+ FIXED_WINDOW = "FIXED_WINDOW",
7
+ SLIDING_WINDOW_LOG = "SLIDING_WINDOW_LOG",
8
+ SLIDING_WINDOW_COUNTER = "SLIDING_WINDOW_COUNTER",
9
+ TOKEN_BUCKET = "TOKEN_BUCKET"
10
+ }
11
+ type RateLimitResult = {
12
+ allowed: boolean;
13
+ limit: number;
14
+ remaining: number;
15
+ /** Epoch ms when the current window resets or limit fully replenishes (best effort). */
16
+ resetMs: number;
17
+ /** Suggested retry-after in ms when throttled. */
18
+ retryAfterMs?: number;
19
+ algorithm: Algorithm;
20
+ };
21
+ type FluxGuardMetricsSnapshot = {
22
+ totalChecks: number;
23
+ allowed: number;
24
+ throttled: number;
25
+ redisErrors: number;
26
+ };
27
+ type FluxGuardEvents = {
28
+ onAllowed?: (key: string, result: RateLimitResult) => void;
29
+ onThrottled?: (key: string, result: RateLimitResult) => void;
30
+ onRedisError?: (err: unknown) => void;
31
+ };
32
+ type FluxGuardConfig = {
33
+ algorithm: Algorithm;
34
+ /** Max requests (or burst capacity for token bucket). */
35
+ limit: number;
36
+ windowMs: number;
37
+ /** Redis connection — omit for in-memory limiting. */
38
+ redis?: RedisOptions | Redis;
39
+ keyPrefix?: string;
40
+ /** Injected clock — default `Date.now`. */
41
+ nowFn?: () => number;
42
+ failOpen?: boolean;
43
+ /** Optional external metrics hook — receives snapshot after each successful check path. */
44
+ metrics?: {
45
+ record?: (s: FluxGuardMetricsSnapshot) => void;
46
+ };
47
+ events?: FluxGuardEvents;
48
+ };
49
+
50
+ declare class FluxGuard {
51
+ private readonly cfg;
52
+ private readonly store;
53
+ private readonly prefix;
54
+ private readonly nowFn;
55
+ private readonly config;
56
+ private metrics;
57
+ constructor(cfg: FluxGuardConfig);
58
+ getMetrics(): FluxGuardMetricsSnapshot;
59
+ /** Merge event recording for external metrics (e.g. Prometheus). */
60
+ onMetricsRecord(fn: (snapshot: FluxGuardMetricsSnapshot) => void): void;
61
+ check(key: string): Promise<RateLimitResult>;
62
+ private checkLocal;
63
+ }
64
+
65
+ /**
66
+ * Storage abstraction: local Map or Redis (Lua via evalScript).
67
+ */
68
+ interface Store {
69
+ get(key: string): Promise<string | null>;
70
+ set(key: string, value: string, ttlMs: number): Promise<void>;
71
+ del(key: string): Promise<void>;
72
+ /**
73
+ * Execute Lua atomically (Redis). Local store may throw if used.
74
+ */
75
+ evalScript(script: string, keys: string[], args: (string | number)[]): Promise<unknown[]>;
76
+ }
77
+
78
+ declare class LocalStore implements Store {
79
+ private readonly clock;
80
+ private readonly data;
81
+ constructor(clock?: () => number);
82
+ get(key: string): Promise<string | null>;
83
+ set(key: string, value: string, ttlMs: number): Promise<void>;
84
+ del(key: string): Promise<void>;
85
+ evalScript(): Promise<unknown[]>;
86
+ /** Test helper: clear all keys */
87
+ _clear(): void;
88
+ }
89
+
90
+ declare class RedisStore implements Store {
91
+ private readonly redis;
92
+ private readonly owns;
93
+ private readonly shaCache;
94
+ constructor(redis: Redis | RedisOptions, optionsIsObject: boolean);
95
+ loadScriptFile(filename: string): string;
96
+ disconnect(): void;
97
+ get redisClient(): Redis;
98
+ get(key: string): Promise<string | null>;
99
+ set(key: string, value: string, ttlMs: number): Promise<void>;
100
+ del(key: string): Promise<void>;
101
+ evalScript(script: string, keys: string[], args: (string | number)[]): Promise<unknown[]>;
102
+ }
103
+
104
+ type ExpressLimiterOptions = {
105
+ keyExtract?: (req: Request) => string;
106
+ onThrottled?: (req: Request, res: Response, result: RateLimitResult) => void;
107
+ skipSuccessfulRequests?: boolean;
108
+ skipFailedRequests?: boolean;
109
+ headers?: boolean;
110
+ };
111
+ declare function fluxGuardMiddleware(limiter: FluxGuard, options?: ExpressLimiterOptions): RequestHandler;
112
+
113
+ type PrometheusMetrics = {
114
+ registry: Registry;
115
+ checksTotal: Counter<string>;
116
+ allowedTotal: Counter<string>;
117
+ throttledTotal: Counter<string>;
118
+ redisErrorsTotal: Counter<string>;
119
+ };
120
+ declare function createFluxGuardPrometheusMetrics(limiter: FluxGuard, options?: {
121
+ prefix?: string;
122
+ collectDefault?: boolean;
123
+ }): PrometheusMetrics;
124
+
125
+ export { Algorithm, type ExpressLimiterOptions, FluxGuard, type FluxGuardConfig, LocalStore, type PrometheusMetrics, type RateLimitResult, RedisStore, type Store, createFluxGuardPrometheusMetrics, fluxGuardMiddleware };