@futdevpro/nts-dynamo 1.15.145 → 1.15.150
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.
- package/.dynamo/logs/cicd-pipeline/output.log +1665 -1803
- package/.dynamo/logs/cicd-pipeline/status.json +33 -33
- package/README.md +16 -0
- package/__documentations/2026-08-21-distributed-rate-limit-bfr-ctm-010.md +59 -0
- package/build/_collections/npm-token-check.util.d.ts +139 -0
- package/build/_collections/npm-token-check.util.d.ts.map +1 -0
- package/build/_collections/npm-token-check.util.js +302 -0
- package/build/_collections/npm-token-check.util.js.map +1 -0
- package/build/_modules/rate-limit/_enums/rate-limit-store-kind.type-enum.d.ts +6 -0
- package/build/_modules/rate-limit/_enums/rate-limit-store-kind.type-enum.d.ts.map +1 -0
- package/build/_modules/rate-limit/_enums/rate-limit-store-kind.type-enum.js +10 -0
- package/build/_modules/rate-limit/_enums/rate-limit-store-kind.type-enum.js.map +1 -0
- package/build/_modules/rate-limit/_models/rate-limit-config.interface.d.ts +15 -6
- package/build/_modules/rate-limit/_models/rate-limit-config.interface.d.ts.map +1 -1
- package/build/_modules/rate-limit/_models/rate-limit-consume-request.interface.d.ts +9 -0
- package/build/_modules/rate-limit/_models/rate-limit-consume-request.interface.d.ts.map +1 -0
- package/build/_modules/rate-limit/_models/rate-limit-consume-request.interface.js +3 -0
- package/build/_modules/rate-limit/_models/rate-limit-consume-request.interface.js.map +1 -0
- package/build/_modules/rate-limit/_models/rate-limit-consume-result.interface.d.ts +7 -0
- package/build/_modules/rate-limit/_models/rate-limit-consume-result.interface.d.ts.map +1 -0
- package/build/_modules/rate-limit/_models/rate-limit-consume-result.interface.js +3 -0
- package/build/_modules/rate-limit/_models/rate-limit-consume-result.interface.js.map +1 -0
- package/build/_modules/rate-limit/_models/rate-limit-store.interface.d.ts +9 -0
- package/build/_modules/rate-limit/_models/rate-limit-store.interface.d.ts.map +1 -0
- package/build/_modules/rate-limit/_models/rate-limit-store.interface.js +3 -0
- package/build/_modules/rate-limit/_models/rate-limit-store.interface.js.map +1 -0
- package/build/_modules/rate-limit/index.d.ts +5 -0
- package/build/_modules/rate-limit/index.d.ts.map +1 -1
- package/build/_modules/rate-limit/index.js +5 -1
- package/build/_modules/rate-limit/index.js.map +1 -1
- package/build/_modules/rate-limit/rate-limit-in-memory.store.d.ts +19 -0
- package/build/_modules/rate-limit/rate-limit-in-memory.store.d.ts.map +1 -0
- package/build/_modules/rate-limit/rate-limit-in-memory.store.js +71 -0
- package/build/_modules/rate-limit/rate-limit-in-memory.store.js.map +1 -0
- package/build/_modules/rate-limit/rate-limit.middleware.d.ts +39 -103
- package/build/_modules/rate-limit/rate-limit.middleware.d.ts.map +1 -1
- package/build/_modules/rate-limit/rate-limit.middleware.js +325 -236
- package/build/_modules/rate-limit/rate-limit.middleware.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/_collections/npm-token-check.util.spec.ts +218 -0
- package/src/_collections/npm-token-check.util.ts +327 -0
- package/src/_modules/rate-limit/_enums/rate-limit-store-kind.type-enum.ts +5 -0
- package/src/_modules/rate-limit/_models/rate-limit-config.interface.ts +19 -6
- package/src/_modules/rate-limit/_models/rate-limit-consume-request.interface.ts +9 -0
- package/src/_modules/rate-limit/_models/rate-limit-consume-result.interface.ts +6 -0
- package/src/_modules/rate-limit/_models/rate-limit-store.interface.ts +9 -0
- package/src/_modules/rate-limit/index.ts +5 -0
- package/src/_modules/rate-limit/rate-limit-in-memory.store.ts +91 -0
- package/src/_modules/rate-limit/rate-limit.middleware.spec.ts +424 -200
- package/src/_modules/rate-limit/rate-limit.middleware.ts +436 -275
- package/src/index.ts +1 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Request } from 'express';
|
|
2
2
|
|
|
3
3
|
import { DyNTS_RateLimit_Policy } from './rate-limit-policy.interface';
|
|
4
|
+
import { DyNTS_RateLimit_Store_Interface } from './rate-limit-store.interface';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Config a `DyNTS_RateLimit_Middleware.configure(...)`-hoz.
|
|
@@ -25,17 +26,17 @@ export interface DyNTS_RateLimit_Config {
|
|
|
25
26
|
* Rate-limit subject-extractor. Visszater a karakterlanccal, ami azonositja
|
|
26
27
|
* a rate-limit alanyat (pl. IP-cim, API-kulcs ID, account ID).
|
|
27
28
|
*
|
|
28
|
-
* Default:
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* adja vissza.
|
|
29
|
+
* Default: az Express altal mar megbizhatonak minositett `req.ip`, vagy ha
|
|
30
|
+
* hianyzik akkor `'unknown'`. A middleware szandekosan nem olvassa kozvetlenul
|
|
31
|
+
* az ugyfel altal hamisithato `x-forwarded-for` headert. Production uzemben a
|
|
32
|
+
* shared-store gate explicit, opaque extractort kovetel.
|
|
33
33
|
*/
|
|
34
34
|
keyExtractor?: (req: Request) => string;
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Endpoint-csoportosito. Visszater a karakterlanccal, ami azonositja az
|
|
38
|
-
* adott endpoint-csoportot. A storage-key
|
|
38
|
+
* adott endpoint-csoportot. A storage-key egyertelmuen hossz-prefixelt
|
|
39
|
+
* subject + endpoint kompozicio lesz.
|
|
39
40
|
*
|
|
40
41
|
* Default: `req.path` — minden uri-path kulon vodorbe kerul. Csoportositas
|
|
41
42
|
* mas szempontok szerint (pl. "datasets" csoport = `/api/datasets/*`)
|
|
@@ -43,6 +44,18 @@ export interface DyNTS_RateLimit_Config {
|
|
|
43
44
|
*/
|
|
44
45
|
endpointGrouper?: (req: Request) => string;
|
|
45
46
|
|
|
47
|
+
/** Optional permit cost per request. Default: one. */
|
|
48
|
+
costExtractor?: (req: Request) => number;
|
|
49
|
+
|
|
50
|
+
/** Atomic store. The default in-memory implementation is for tests and single-instance development only. */
|
|
51
|
+
store?: DyNTS_RateLimit_Store_Interface;
|
|
52
|
+
|
|
53
|
+
/** Fail configuration unless a shared store plus explicit opaque key/group extractors are supplied. */
|
|
54
|
+
requireSharedStore?: boolean;
|
|
55
|
+
|
|
56
|
+
/** Deterministic clock seam. Production callers normally omit it. */
|
|
57
|
+
clock?: () => number;
|
|
58
|
+
|
|
46
59
|
/**
|
|
47
60
|
* Beallitja-e a `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
|
|
48
61
|
* `X-RateLimit-Reset` (es a 429-re `Retry-After`) response header-eket.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DyNTS_RateLimit_Policy } from './rate-limit-policy.interface';
|
|
2
|
+
|
|
3
|
+
/** Atomic permit request passed to a rate-limit store. */
|
|
4
|
+
export interface DyNTS_RateLimit_ConsumeRequest_Interface {
|
|
5
|
+
bucketKey: string;
|
|
6
|
+
policy: DyNTS_RateLimit_Policy;
|
|
7
|
+
cost: number;
|
|
8
|
+
now: number;
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DyNTS_RateLimitStoreKind_Type } from '../_enums/rate-limit-store-kind.type-enum';
|
|
2
|
+
import { DyNTS_RateLimit_ConsumeRequest_Interface } from './rate-limit-consume-request.interface';
|
|
3
|
+
import { DyNTS_RateLimit_ConsumeResult_Interface } from './rate-limit-consume-result.interface';
|
|
4
|
+
|
|
5
|
+
/** Atomic persistence seam. Production hosts can require `shared` scope at configuration time. */
|
|
6
|
+
export interface DyNTS_RateLimit_Store_Interface {
|
|
7
|
+
readonly storeKind: DyNTS_RateLimitStoreKind_Type;
|
|
8
|
+
consume(request: DyNTS_RateLimit_ConsumeRequest_Interface): Promise<DyNTS_RateLimit_ConsumeResult_Interface>;
|
|
9
|
+
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export { DyNTS_RateLimit_Middleware } from './rate-limit.middleware';
|
|
2
|
+
export { DyNTS_RateLimit_InMemoryStore } from './rate-limit-in-memory.store';
|
|
2
3
|
export { DyNTS_RateLimit_Config } from './_models/rate-limit-config.interface';
|
|
4
|
+
export { DyNTS_RateLimit_ConsumeRequest_Interface } from './_models/rate-limit-consume-request.interface';
|
|
5
|
+
export { DyNTS_RateLimit_ConsumeResult_Interface } from './_models/rate-limit-consume-result.interface';
|
|
3
6
|
export { DyNTS_RateLimit_Policy } from './_models/rate-limit-policy.interface';
|
|
7
|
+
export { DyNTS_RateLimit_Store_Interface } from './_models/rate-limit-store.interface';
|
|
8
|
+
export { DyNTS_RateLimitStoreKind_Type } from './_enums/rate-limit-store-kind.type-enum';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { DyNTS_RateLimitStoreKind_Type } from './_enums/rate-limit-store-kind.type-enum';
|
|
2
|
+
import { DyNTS_RateLimit_ConsumeRequest_Interface } from './_models/rate-limit-consume-request.interface';
|
|
3
|
+
import { DyNTS_RateLimit_ConsumeResult_Interface } from './_models/rate-limit-consume-result.interface';
|
|
4
|
+
import { DyNTS_RateLimit_Store_Interface } from './_models/rate-limit-store.interface';
|
|
5
|
+
|
|
6
|
+
interface DyNTS_RateLimit_InMemoryRecord_Interface {
|
|
7
|
+
timestamps: number[];
|
|
8
|
+
windowMs: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Deterministic single-process store for tests and explicitly single-instance development. */
|
|
12
|
+
export class DyNTS_RateLimit_InMemoryStore implements DyNTS_RateLimit_Store_Interface {
|
|
13
|
+
public readonly storeKind: DyNTS_RateLimitStoreKind_Type = DyNTS_RateLimitStoreKind_Type.inMemory;
|
|
14
|
+
private readonly records: Map<string, DyNTS_RateLimit_InMemoryRecord_Interface> = new Map();
|
|
15
|
+
|
|
16
|
+
/** Consumes permit units with exact sliding-window semantics and no background cleanup task. */
|
|
17
|
+
public async consume(
|
|
18
|
+
request: DyNTS_RateLimit_ConsumeRequest_Interface
|
|
19
|
+
): Promise<DyNTS_RateLimit_ConsumeResult_Interface> {
|
|
20
|
+
this.assertRequest(request);
|
|
21
|
+
|
|
22
|
+
const windowStart: number = request.now - request.policy.windowMs;
|
|
23
|
+
const existing: DyNTS_RateLimit_InMemoryRecord_Interface | undefined = this.records.get(request.bucketKey);
|
|
24
|
+
const recent: number[] = (existing?.timestamps ?? []).filter(
|
|
25
|
+
(timestamp: number): boolean => timestamp > windowStart
|
|
26
|
+
);
|
|
27
|
+
const allowed: boolean = recent.length + request.cost <= request.policy.limit;
|
|
28
|
+
|
|
29
|
+
if (allowed) {
|
|
30
|
+
for (let index: number = 0; index < request.cost; index += 1) {
|
|
31
|
+
recent.push(request.now);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.records.set(request.bucketKey, {
|
|
36
|
+
timestamps: recent,
|
|
37
|
+
windowMs: request.policy.windowMs,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
allowed: allowed,
|
|
42
|
+
remaining: Math.max(0, request.policy.limit - recent.length),
|
|
43
|
+
resetAt: (recent[0] ?? request.now) + request.policy.windowMs,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Opportunistically removes expired local records when explicitly invoked by the host. */
|
|
48
|
+
public cleanup(now: number): void {
|
|
49
|
+
for (const entry of this.records.entries()) {
|
|
50
|
+
const key: string = entry[0];
|
|
51
|
+
const record: DyNTS_RateLimit_InMemoryRecord_Interface = entry[1];
|
|
52
|
+
const recent: number[] = record.timestamps.filter(
|
|
53
|
+
(timestamp: number): boolean => timestamp > now - record.windowMs
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (!recent.length) {
|
|
57
|
+
this.records.delete(key);
|
|
58
|
+
} else {
|
|
59
|
+
this.records.set(key, { ...record, timestamps: recent });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Clears all local state for deterministic tests. */
|
|
65
|
+
public reset(): void {
|
|
66
|
+
this.records.clear();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Returns only the aggregate local bucket count for diagnostics. */
|
|
70
|
+
public getTrackedBucketCount(): number {
|
|
71
|
+
return this.records.size;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private assertRequest(request: DyNTS_RateLimit_ConsumeRequest_Interface): void {
|
|
75
|
+
const isValid: boolean = Boolean(request.bucketKey.trim())
|
|
76
|
+
&& request.bucketKey.length <= 512
|
|
77
|
+
&& Number.isSafeInteger(request.now)
|
|
78
|
+
&& request.now >= 0
|
|
79
|
+
&& Number.isSafeInteger(request.cost)
|
|
80
|
+
&& request.cost > 0
|
|
81
|
+
&& Number.isSafeInteger(request.policy.limit)
|
|
82
|
+
&& request.policy.limit > 0
|
|
83
|
+
&& Number.isSafeInteger(request.policy.windowMs)
|
|
84
|
+
&& request.policy.windowMs > 0
|
|
85
|
+
&& request.cost <= request.policy.limit;
|
|
86
|
+
|
|
87
|
+
if (!isValid) {
|
|
88
|
+
throw new Error('DYNTS_RATE_LIMIT_STORE_REQUEST_INVALID');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|