@edirect/rate-limit-module 11.0.65 → 11.0.66
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/package.json +12 -29
- package/dist/README.md +0 -584
- package/dist/package.json +0 -41
- package/dist/tsconfig.lib.tsbuildinfo +0 -1
- /package/{dist/src → src}/decorators/rate-limiting-rule.decorator.d.ts +0 -0
- /package/{dist/src → src}/decorators/rate-limiting-rule.decorator.d.ts.map +0 -0
- /package/{dist/src → src}/decorators/rate-limiting-rule.decorator.js +0 -0
- /package/{dist/src → src}/guards/rate-limiting.guard.d.ts +0 -0
- /package/{dist/src → src}/guards/rate-limiting.guard.d.ts.map +0 -0
- /package/{dist/src → src}/guards/rate-limiting.guard.js +0 -0
- /package/{dist/src → src}/index.d.ts +0 -0
- /package/{dist/src → src}/index.d.ts.map +0 -0
- /package/{dist/src → src}/index.js +0 -0
- /package/{dist/src → src}/main.d.ts +0 -0
- /package/{dist/src → src}/main.d.ts.map +0 -0
- /package/{dist/src → src}/main.js +0 -0
- /package/{dist/src → src}/object.utils.d.ts +0 -0
- /package/{dist/src → src}/object.utils.d.ts.map +0 -0
- /package/{dist/src → src}/object.utils.js +0 -0
- /package/{dist/src → src}/rate-limiting.module.d.ts +0 -0
- /package/{dist/src → src}/rate-limiting.module.d.ts.map +0 -0
- /package/{dist/src → src}/rate-limiting.module.js +0 -0
- /package/{dist/src → src}/rules/dto/pagination.dto.d.ts +0 -0
- /package/{dist/src → src}/rules/dto/pagination.dto.d.ts.map +0 -0
- /package/{dist/src → src}/rules/dto/pagination.dto.js +0 -0
- /package/{dist/src → src}/rules/dto/rule.dto.d.ts +0 -0
- /package/{dist/src → src}/rules/dto/rule.dto.d.ts.map +0 -0
- /package/{dist/src → src}/rules/dto/rule.dto.js +0 -0
- /package/{dist/src → src}/rules/interface/pagination.interface.d.ts +0 -0
- /package/{dist/src → src}/rules/interface/pagination.interface.d.ts.map +0 -0
- /package/{dist/src → src}/rules/interface/pagination.interface.js +0 -0
- /package/{dist/src → src}/rules/interface/rule.interface.d.ts +0 -0
- /package/{dist/src → src}/rules/interface/rule.interface.d.ts.map +0 -0
- /package/{dist/src → src}/rules/interface/rule.interface.js +0 -0
- /package/{dist/src → src}/rules/rule.controller.d.ts +0 -0
- /package/{dist/src → src}/rules/rule.controller.d.ts.map +0 -0
- /package/{dist/src → src}/rules/rule.controller.js +0 -0
- /package/{dist/src → src}/rules/rule.module.d.ts +0 -0
- /package/{dist/src → src}/rules/rule.module.d.ts.map +0 -0
- /package/{dist/src → src}/rules/rule.module.js +0 -0
- /package/{dist/src → src}/rules/rule.repository.d.ts +0 -0
- /package/{dist/src → src}/rules/rule.repository.d.ts.map +0 -0
- /package/{dist/src → src}/rules/rule.repository.js +0 -0
- /package/{dist/src → src}/rules/rule.schema.d.ts +0 -0
- /package/{dist/src → src}/rules/rule.schema.d.ts.map +0 -0
- /package/{dist/src → src}/rules/rule.schema.js +0 -0
- /package/{dist/src → src}/rules/rule.service.d.ts +0 -0
- /package/{dist/src → src}/rules/rule.service.d.ts.map +0 -0
- /package/{dist/src → src}/rules/rule.service.js +0 -0
- /package/{dist/src → src}/shared/cache/cache.service.d.ts +0 -0
- /package/{dist/src → src}/shared/cache/cache.service.d.ts.map +0 -0
- /package/{dist/src → src}/shared/cache/cache.service.js +0 -0
- /package/{dist/src → src}/shared/dsl/dsl.interface.d.ts +0 -0
- /package/{dist/src → src}/shared/dsl/dsl.interface.d.ts.map +0 -0
- /package/{dist/src → src}/shared/dsl/dsl.interface.js +0 -0
- /package/{dist/src → src}/shared/dsl/dsl.service.d.ts +0 -0
- /package/{dist/src → src}/shared/dsl/dsl.service.d.ts.map +0 -0
- /package/{dist/src → src}/shared/dsl/dsl.service.js +0 -0
- /package/{dist/src → src}/shared/redis/redis.module.d.ts +0 -0
- /package/{dist/src → src}/shared/redis/redis.module.d.ts.map +0 -0
- /package/{dist/src → src}/shared/redis/redis.module.js +0 -0
- /package/{dist/src → src}/shared/redis/redis.service.d.ts +0 -0
- /package/{dist/src → src}/shared/redis/redis.service.d.ts.map +0 -0
- /package/{dist/src → src}/shared/redis/redis.service.js +0 -0
- /package/{dist/src → src}/shared/rule-engine/interface/rule-engine.interface.d.ts +0 -0
- /package/{dist/src → src}/shared/rule-engine/interface/rule-engine.interface.d.ts.map +0 -0
- /package/{dist/src → src}/shared/rule-engine/interface/rule-engine.interface.js +0 -0
- /package/{dist/src → src}/shared/rule-engine/rule-engine.module.d.ts +0 -0
- /package/{dist/src → src}/shared/rule-engine/rule-engine.module.d.ts.map +0 -0
- /package/{dist/src → src}/shared/rule-engine/rule-engine.module.js +0 -0
- /package/{dist/src → src}/shared/rule-engine/rule-engine.service.d.ts +0 -0
- /package/{dist/src → src}/shared/rule-engine/rule-engine.service.d.ts.map +0 -0
- /package/{dist/src → src}/shared/rule-engine/rule-engine.service.js +0 -0
- /package/{dist/src → src}/shared/shared.module.d.ts +0 -0
- /package/{dist/src → src}/shared/shared.module.d.ts.map +0 -0
- /package/{dist/src → src}/shared/shared.module.js +0 -0
- /package/{dist/src → src}/validators/rule.constraint.d.ts +0 -0
- /package/{dist/src → src}/validators/rule.constraint.d.ts.map +0 -0
- /package/{dist/src → src}/validators/rule.constraint.js +0 -0
- /package/{dist/src → src}/validators/rule.validator.d.ts +0 -0
- /package/{dist/src → src}/validators/rule.validator.d.ts.map +0 -0
- /package/{dist/src → src}/validators/rule.validator.js +0 -0
package/package.json
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@edirect/rate-limit-module",
|
|
3
|
-
"version": "11.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"types": "./dist/src/index.d.ts",
|
|
3
|
+
"version": "11.0.66",
|
|
4
|
+
"main": "./src/index.js",
|
|
5
|
+
"types": "./src/index.d.ts",
|
|
7
6
|
"exports": {
|
|
8
7
|
".": {
|
|
9
|
-
"import": "./
|
|
10
|
-
"default": "./
|
|
11
|
-
"require": "./
|
|
12
|
-
"types": "./
|
|
8
|
+
"import": "./src/index.js",
|
|
9
|
+
"default": "./src/index.js",
|
|
10
|
+
"require": "./src/index.js",
|
|
11
|
+
"types": "./src/index.d.ts"
|
|
13
12
|
},
|
|
14
13
|
"./package.json": "./package.json"
|
|
15
14
|
},
|
|
16
15
|
"files": [
|
|
17
|
-
"
|
|
16
|
+
"src"
|
|
18
17
|
],
|
|
19
18
|
"dependencies": {
|
|
19
|
+
"@edirect/auth": "11.0.66",
|
|
20
|
+
"@edirect/logger": "11.0.66",
|
|
20
21
|
"@nestjs/cache-manager": "^3.1.3",
|
|
21
22
|
"@nestjs/common": "^11.2.1",
|
|
22
23
|
"@nestjs/config": "^4.0.4",
|
|
@@ -31,28 +32,10 @@
|
|
|
31
32
|
"jexl": "^2.3.0",
|
|
32
33
|
"mongoose": "^9.9.3",
|
|
33
34
|
"tslib": "^2.8.1",
|
|
34
|
-
"uuid": "^14.0.1"
|
|
35
|
-
"@edirect/auth": "11.0.65",
|
|
36
|
-
"@edirect/logger": "11.0.65"
|
|
35
|
+
"uuid": "^14.0.1"
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@types/jexl": "^2.3.4"
|
|
40
39
|
},
|
|
41
|
-
"
|
|
42
|
-
"name": "@edirect/rate-limit-module",
|
|
43
|
-
"targets": {
|
|
44
|
-
"build": {
|
|
45
|
-
"executor": "@nx/js:tsc",
|
|
46
|
-
"options": {
|
|
47
|
-
"main": "{workspaceRoot}/packages/edirect-rate-limit-module/src/index.ts",
|
|
48
|
-
"tsConfig": "{workspaceRoot}/packages/edirect-rate-limit-module/tsconfig.lib.json",
|
|
49
|
-
"outputPath": "{workspaceRoot}/packages/edirect-rate-limit-module/dist",
|
|
50
|
-
"assets": [
|
|
51
|
-
"{workspaceRoot}/packages/edirect-rate-limit-module/package.json",
|
|
52
|
-
"{workspaceRoot}/packages/edirect-rate-limit-module/README.md"
|
|
53
|
-
]
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
40
|
+
"type": "commonjs"
|
|
58
41
|
}
|
package/dist/README.md
DELETED
|
@@ -1,584 +0,0 @@
|
|
|
1
|
-
# @edirect/rate-limit-module
|
|
2
|
-
|
|
3
|
-
A flexible **rate-limiting system** where access to operations can be restricted based on dynamic **rule expressions** such as `count_quote( ) > 1`. If the condition evaluates to `true`, access is **blocked**.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
pnpm add @edirect/rate-limit-module
|
|
9
|
-
# or
|
|
10
|
-
npm install @edirect/rate-limit-module
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## 🚀 Quick Start
|
|
16
|
-
|
|
17
|
-
### 1. Create a Global Database Connection Module
|
|
18
|
-
|
|
19
|
-
**Important:** This module requires a MongoDB connection provider. Create a global module to provide it:
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
// db-provider.module.ts
|
|
23
|
-
import { Global, Module } from '@nestjs/common';
|
|
24
|
-
import { Connection } from 'mongoose';
|
|
25
|
-
import { MongoDBConnection } from './mongodb-connection';
|
|
26
|
-
|
|
27
|
-
export const dbProvider = [
|
|
28
|
-
{
|
|
29
|
-
provide: 'DATABASE_CONNECTION',
|
|
30
|
-
useFactory: async (): Promise<Connection> => {
|
|
31
|
-
return new MongoDBConnection().connect();
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
@Global()
|
|
37
|
-
@Module({
|
|
38
|
-
providers: [...dbProvider],
|
|
39
|
-
exports: [...dbProvider],
|
|
40
|
-
})
|
|
41
|
-
export class DbProviderModule {}
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### 2. Import Database Module in Root Module
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
// app.module.ts
|
|
48
|
-
import { Module } from '@nestjs/common';
|
|
49
|
-
import { DbProviderModule } from './db-provider.module';
|
|
50
|
-
|
|
51
|
-
@Module({
|
|
52
|
-
imports: [
|
|
53
|
-
DbProviderModule, // Must be imported first
|
|
54
|
-
// ... other modules
|
|
55
|
-
],
|
|
56
|
-
})
|
|
57
|
-
export class AppModule {}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### 3. Import RateLimitingModule in Feature Module
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
import {
|
|
64
|
-
RateLimitingModule,
|
|
65
|
-
RuleEngineModule,
|
|
66
|
-
} from '@edirect/rate-limit-module';
|
|
67
|
-
|
|
68
|
-
@Module({
|
|
69
|
-
imports: [
|
|
70
|
-
// Pass the token name of your existing MongoDB connection provider
|
|
71
|
-
// Default is 'DATABASE_CONNECTION'
|
|
72
|
-
RateLimitingModule.forRoot('DATABASE_CONNECTION'),
|
|
73
|
-
|
|
74
|
-
// Or use a custom token
|
|
75
|
-
RateLimitingModule.forRoot('MY_CUSTOM_DB_CONNECTION'),
|
|
76
|
-
|
|
77
|
-
// Or omit to use default
|
|
78
|
-
RateLimitingModule.forRoot(),
|
|
79
|
-
|
|
80
|
-
// Configure custom functions for rule evaluation
|
|
81
|
-
RuleEngineModule.forFeatureAsync({
|
|
82
|
-
imports: [YourFunctionsModule],
|
|
83
|
-
useFactory: async (useCase: YourInjectFunctionsUseCase) => {
|
|
84
|
-
return await useCase.getFunctions();
|
|
85
|
-
},
|
|
86
|
-
inject: [YourInjectFunctionsUseCase],
|
|
87
|
-
}),
|
|
88
|
-
],
|
|
89
|
-
})
|
|
90
|
-
export class YourFeatureModule {}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
### 2. Database Connection Requirement
|
|
94
|
-
|
|
95
|
-
The module uses the global `DATABASE_CONNECTION` provider created in step 1. It references your connection using the token name, so it doesn't duplicate the connection.
|
|
96
|
-
|
|
97
|
-
### 3. Apply Rate Limiting to Routes
|
|
98
|
-
|
|
99
|
-
Use the `@RateLimitingRuleMetadata` decorator and `@UseGuards(RateLimitingRuleGuard)` to protect your endpoints:
|
|
100
|
-
|
|
101
|
-
```ts
|
|
102
|
-
import { RateLimitingRuleGuard, RateLimitingRuleMetadata } from '@edirect/rate-limit-module';
|
|
103
|
-
|
|
104
|
-
@RateLimitingRuleMetadata({
|
|
105
|
-
action: 'create_quote',
|
|
106
|
-
partnerPath: 'partner.partnerId',
|
|
107
|
-
providerPath: 'provider',
|
|
108
|
-
productTypePath: 'productType',
|
|
109
|
-
marketPath: 'market',
|
|
110
|
-
})
|
|
111
|
-
@UseGuards(RateLimitingRuleGuard)
|
|
112
|
-
@Post('quotes')
|
|
113
|
-
createQuote(@Body() body: CreateQuoteDto) {
|
|
114
|
-
return this.quoteService.create(body);
|
|
115
|
-
}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## ✨ Features
|
|
119
|
-
|
|
120
|
-
- Define rate-limiting rules with custom expressions
|
|
121
|
-
- Dynamically inject context-aware functions for rule evaluation
|
|
122
|
-
- Easily integrate with any resource via decorators
|
|
123
|
-
- Uses existing MongoDB connection (no separate connection required)
|
|
124
|
-
- Global module - configure once, use everywhere
|
|
125
|
-
- Built with extensibility and modularity in mind
|
|
126
|
-
|
|
127
|
-
## 🧱 How It Works
|
|
128
|
-
|
|
129
|
-
1. You define a rule using a simple expression (e.g., `count_quote( ) > 1`)
|
|
130
|
-
2. The rule is evaluated at runtime using data injected from the request context
|
|
131
|
-
3. If the expression is true, the action is blocked
|
|
132
|
-
|
|
133
|
-
## 🛠 Architecture
|
|
134
|
-
|
|
135
|
-
The module is designed to:
|
|
136
|
-
|
|
137
|
-
- **Not create its own MongoDB connection** - it uses the application's existing connection
|
|
138
|
-
- **Be a global module** - providers are available throughout the application
|
|
139
|
-
- **Use custom model providers** - creates Mongoose models using the injected `DATABASE_CONNECTION`
|
|
140
|
-
- **Export Reflector** - ensures guards have access to metadata
|
|
141
|
-
|
|
142
|
-
## 📝 Configuration Details
|
|
143
|
-
|
|
144
|
-
### Module Structure
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
RateLimitingModule.forRoot(dbProvider) // Global module, accepts DATABASE_CONNECTION provider
|
|
148
|
-
└─ RuleModule.register() // Provides RuleService and RuleRepository
|
|
149
|
-
└─ Custom Model Provider // Uses DATABASE_CONNECTION to create Mongoose models
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Required Providers
|
|
153
|
-
|
|
154
|
-
The module requires:
|
|
155
|
-
|
|
156
|
-
1. **DATABASE_CONNECTION**: Mongoose connection instance
|
|
157
|
-
2. **Reflector**: For metadata reflection (provided by the module)
|
|
158
|
-
3. **RuleService**: For accessing rate limiting rules (exported by RuleModule)
|
|
159
|
-
|
|
160
|
-
````ts
|
|
161
|
-
import { RateLimitingRuleMetadata } from 'path-to-rate-limiting-module';
|
|
162
|
-
|
|
163
|
-
- `action`: Name of the action to be controlled.
|
|
164
|
-
|
|
165
|
-
- `partnerPath`: Path to extract the partner ID from the request context.
|
|
166
|
-
|
|
167
|
-
- `providerPath`: Path to extract the provider.
|
|
168
|
-
|
|
169
|
-
- `productTypePath`: Path to extract the product type.
|
|
170
|
-
|
|
171
|
-
## 🛠 Rule Engine Integration
|
|
172
|
-
|
|
173
|
-
This module depends on an underlying rule engine to evaluate expressions. You must provide the functions used in expressions through:
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
RuleEngineModule.forFeatureAsync({
|
|
177
|
-
useFactory: async (useCase: RateLimitingInjectFunctionsUseCase) => {
|
|
178
|
-
return await useCase.getFunctions();
|
|
179
|
-
|
|
180
|
-
// returns an object like { count_quote: async () => number }
|
|
181
|
-
},
|
|
182
|
-
|
|
183
|
-
inject: [RateLimitingInjectFunctionsUseCase],
|
|
184
|
-
});
|
|
185
|
-
````
|
|
186
|
-
|
|
187
|
-
## 🧪 Example Rule
|
|
188
|
-
|
|
189
|
-
A rule stored in your system might look like:
|
|
190
|
-
|
|
191
|
-
```json
|
|
192
|
-
{
|
|
193
|
-
"expression": "count_quote() > 1",
|
|
194
|
-
"action": "create_quote"
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
If the expression returns `true`, the rate limiter will block further `create_quote` actions for the user based on the context values.
|
|
199
|
-
|
|
200
|
-
## 🧠 Dynamic Parameter Resolution
|
|
201
|
-
|
|
202
|
-
When building queries based on request context, it's possible to reference nested values using paths. This system supports:
|
|
203
|
-
|
|
204
|
-
- Resolving values using variable placeholders like `:applicant.documents[0].value`.
|
|
205
|
-
- Interpreting date expressions safely, like `date(-5d)` (5 days ago), without using `eval`.
|
|
206
|
-
|
|
207
|
-
### Supported Syntax
|
|
208
|
-
|
|
209
|
-
```json
|
|
210
|
-
{
|
|
211
|
-
"value": ":applicant.documents[0].value",
|
|
212
|
-
"label": "applicant.documents.value"
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Will extract the value from the context:
|
|
217
|
-
|
|
218
|
-
```json
|
|
219
|
-
{
|
|
220
|
-
"applicant": {
|
|
221
|
-
"documents": [{ "type": "NIF", "value": "ABCDE" }]
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
Result: `"ABCDE"`
|
|
227
|
-
|
|
228
|
-
You can also use dynamic dates:
|
|
229
|
-
|
|
230
|
-
```json
|
|
231
|
-
{
|
|
232
|
-
"value": "date(-7d)",
|
|
233
|
-
"label": "createdAt",
|
|
234
|
-
"op": "gte"
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Result: `"2025-07-10T00:00:00.000Z"` (7 days ago)
|
|
239
|
-
|
|
240
|
-
### Supported Date Units
|
|
241
|
-
|
|
242
|
-
- `d`: Days (default)
|
|
243
|
-
- `M`: Months
|
|
244
|
-
- `y`: Years
|
|
245
|
-
|
|
246
|
-
Examples:
|
|
247
|
-
|
|
248
|
-
- `date(-5d)` → 5 days ago
|
|
249
|
-
- `date(-1M)` → 1 month ago
|
|
250
|
-
- `date(0)` → Today
|
|
251
|
-
|
|
252
|
-
No dynamic code execution (`eval`, `new Function`) is used — expressions are parsed securely.
|
|
253
|
-
|
|
254
|
-
## ⚙️ Supported Operators (`op`)
|
|
255
|
-
|
|
256
|
-
You can optionally define an `op` (operation) field to specify how the value should be interpreted. The system currently supports:
|
|
257
|
-
|
|
258
|
-
### `gte` — Greater Than or Equal (usually for dates)
|
|
259
|
-
|
|
260
|
-
```json
|
|
261
|
-
{
|
|
262
|
-
"value": "date(-7d)",
|
|
263
|
-
"label": "createdAt",
|
|
264
|
-
"op": "gte"
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
🔍 This means: only include records created after **7 days ago**.
|
|
269
|
-
|
|
270
|
-
```json
|
|
271
|
-
{
|
|
272
|
-
"value": {
|
|
273
|
-
"from": 1,
|
|
274
|
-
"to": 10
|
|
275
|
-
},
|
|
276
|
-
"label": "createdAt",
|
|
277
|
-
"op": "range"
|
|
278
|
-
}
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
🔍 This sets a static date or value range. Can be used for numbers, dates, or custom fields.
|
|
282
|
-
|
|
283
|
-
### `rangeDate` — Dynamic Date Range
|
|
284
|
-
|
|
285
|
-
```json
|
|
286
|
-
{
|
|
287
|
-
"value": {
|
|
288
|
-
"from": "date(-30d)",
|
|
289
|
-
"to": "date(0)"
|
|
290
|
-
},
|
|
291
|
-
"label": "createdAt",
|
|
292
|
-
"op": "rangeDate"
|
|
293
|
-
}
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
🔍 This automatically resolves both `from` and `to` values as dates. For example, this would generate a date range from **30 days ago** to **now**.
|
|
297
|
-
|
|
298
|
-
## 🧬 DSL Support and MongoDB Integration
|
|
299
|
-
|
|
300
|
-
This module uses a lightweight **DSL (Domain-Specific Language)** to define rules and filters in a declarative and expressive way.
|
|
301
|
-
|
|
302
|
-
### What is a DSL?
|
|
303
|
-
|
|
304
|
-
A **DSL** is a simplified, pseudo-language designed for expressing business logic in a readable and configurable format. In this module, the DSL is used to define:
|
|
305
|
-
|
|
306
|
-
- Conditional expressions (e.g., `count_quote() > 1`)
|
|
307
|
-
- Filter structures using dynamic context variables and operators (`eq`, `range`, `gte`, etc.)
|
|
308
|
-
|
|
309
|
-
These DSL expressions are **not executed as raw code**, but safely parsed and evaluated using resolvers and expression evaluators.
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
### DSL to MongoDB Filter Conversion
|
|
314
|
-
|
|
315
|
-
One powerful feature of this module is the ability to **transform DSL filters into MongoDB-compatible query objects**.
|
|
316
|
-
|
|
317
|
-
For example, the following DSL input:
|
|
318
|
-
|
|
319
|
-
```json
|
|
320
|
-
{
|
|
321
|
-
createdAt: {
|
|
322
|
-
$gte: new Date('2025-06-17T00:00:00.000Z'),
|
|
323
|
-
$lte: new Date('2025-07-17T00:00:00.000Z')
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
This is handled by utility functions like `buildDlsOp`, which map DSL constructs to database query.
|
|
329
|
-
|
|
330
|
-
### ✅ Benefits of Using DSL
|
|
331
|
-
|
|
332
|
-
- **Separation of logic from code:** Business rules can be stored in DB or configs
|
|
333
|
-
- **Extensibility:** Easy to support new operators or DB backends (e.g., SQL, Elasticsearch)
|
|
334
|
-
- **Security:** No raw `eval` or dynamic JS execution
|
|
335
|
-
- **Readability:** DSL is expressive for non-technical stakeholders
|
|
336
|
-
|
|
337
|
-
By combining DSL definitions with path-based context extraction and safe runtime resolution, this module provides a highly flexible and secure rate-limiting engine ready for dynamic environments.
|
|
338
|
-
|
|
339
|
-
## 🧾 Dynamic Query Context with `variables.queries`
|
|
340
|
-
|
|
341
|
-
The `variables` object is designed to allow structured dynamic query generation through an array of `queries`. Each query defines:
|
|
342
|
-
|
|
343
|
-
- The **function(s)** where this context will be injected (`fnContext`)
|
|
344
|
-
- The **parameters** to be resolved and passed (`query`)
|
|
345
|
-
- **Optional operations** and **default values**
|
|
346
|
-
|
|
347
|
-
### Structure of `variables.queries`
|
|
348
|
-
|
|
349
|
-
```json
|
|
350
|
-
{
|
|
351
|
-
"queries": [
|
|
352
|
-
{
|
|
353
|
-
"fnContext": ["countQuoteByParams"],
|
|
354
|
-
"query": [
|
|
355
|
-
{
|
|
356
|
-
"value": ":partner.partnerId",
|
|
357
|
-
"label": "partner.partnerId"
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
"value": ":productType",
|
|
361
|
-
"label": "productType"
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
"value": ":provider",
|
|
365
|
-
"label": "provider"
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
"value": ":applicant.documents[0].value",
|
|
369
|
-
"label": "applicant.documents.value"
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
"value": ":applicant.documents[0].type",
|
|
373
|
-
"label": "applicant.documents.type"
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
"label": "createdAt",
|
|
377
|
-
"op": "range",
|
|
378
|
-
"value": {
|
|
379
|
-
"to": "date(0)",
|
|
380
|
-
"from": "fn:getValueFromSale"
|
|
381
|
-
},
|
|
382
|
-
"defaultValue": {
|
|
383
|
-
"from": "date(-5d)",
|
|
384
|
-
"to": "date(0)"
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
]
|
|
388
|
-
}
|
|
389
|
-
]
|
|
390
|
-
}
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
### 🔍 Explanation
|
|
394
|
-
|
|
395
|
-
Field
|
|
396
|
-
|
|
397
|
-
Description
|
|
398
|
-
|
|
399
|
-
`fnContext`
|
|
400
|
-
|
|
401
|
-
The name(s) of the function(s) that will receive this query as parameter input
|
|
402
|
-
|
|
403
|
-
`query`
|
|
404
|
-
|
|
405
|
-
Array of parameter definitions. Each will be resolved based on context or DSL expressions
|
|
406
|
-
|
|
407
|
-
`label`
|
|
408
|
-
|
|
409
|
-
The target field to be passed (e.g. used to generate a MongoDB query or as input to a service)
|
|
410
|
-
|
|
411
|
-
`value`
|
|
412
|
-
|
|
413
|
-
Can be a dynamic reference (e.g., `:context.path`), a static value, or a DSL expression
|
|
414
|
-
|
|
415
|
-
`op`
|
|
416
|
-
|
|
417
|
-
Optional. Defines the operator (`eq`, `range`, `rangeDate`, `gte`, etc.)
|
|
418
|
-
|
|
419
|
-
`defaultValue`
|
|
420
|
-
|
|
421
|
-
Fallback value(s) to be used if dynamic resolution fails (e.g., missing path or function)
|
|
422
|
-
|
|
423
|
-
---
|
|
424
|
-
|
|
425
|
-
### 🧠 Special Value Formats
|
|
426
|
-
|
|
427
|
-
- `:path.to.value` — Dynamically extracted from the context object
|
|
428
|
-
- `date(-7d)` — Resolved as a safe date expression
|
|
429
|
-
- `fn:getValueFromSale` — Calls a predefined function to resolve the value dynamically
|
|
430
|
-
- Static values like `"moneyhero"` are passed as-is
|
|
431
|
-
|
|
432
|
-
### 🧪 Use Case Example
|
|
433
|
-
|
|
434
|
-
Imagine your service defines a method like:
|
|
435
|
-
|
|
436
|
-
async countQuoteByParams(filters: Record<string, any>): Promise<number>
|
|
437
|
-
|
|
438
|
-
At runtime, the module will:
|
|
439
|
-
|
|
440
|
-
1. Resolve each `value` in the `query` array using context and supported expressions.
|
|
441
|
-
2. Construct a query object:
|
|
442
|
-
|
|
443
|
-
```json
|
|
444
|
-
{
|
|
445
|
-
partner: { partnerId: "bolttech" },
|
|
446
|
-
productType: "car-insurance",
|
|
447
|
-
provider: "bolttech",
|
|
448
|
-
applicant: {
|
|
449
|
-
documents: [{ type: "NIF", value: "ABCDE" }]
|
|
450
|
-
},
|
|
451
|
-
createdAt: {
|
|
452
|
-
from: new Date("2025-07-25T00:00:00Z"), // resolved from fn:getValueFromSale
|
|
453
|
-
to: new Date("2025-07-30T00:00:00Z") // resolved from date(0)
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
3. Inject this into `countQuoteByParams(...)`
|
|
459
|
-
This approach gives you a highly flexible, configurable system for controlling dynamic function parameters via context-based DSLs and runtime evaluation.
|
|
460
|
-
|
|
461
|
-
---
|
|
462
|
-
|
|
463
|
-
## 🧠 Custom Runtime Functions with `variables.functions`
|
|
464
|
-
|
|
465
|
-
In addition to defining query parameters in `variables.queries`, the system also supports **custom runtime functions** through the `variables.functions` array.
|
|
466
|
-
|
|
467
|
-
These functions can be executed **before query construction** to compute values that are **injected** dynamically into query fields.
|
|
468
|
-
|
|
469
|
-
---
|
|
470
|
-
|
|
471
|
-
### 🔧 Structure of `functions`
|
|
472
|
-
|
|
473
|
-
Each function definition includes:
|
|
474
|
-
|
|
475
|
-
| Field | Description |
|
|
476
|
-
| ----------- | -------------------------------------------------------------------------- |
|
|
477
|
-
| `name` | Unique function identifier |
|
|
478
|
-
| `fn` | The function to execute, in the format `functionName(args...)` |
|
|
479
|
-
| `variables` | Parameters passed to the function, which can include subqueries and values |
|
|
480
|
-
|
|
481
|
-
### 📌 Example
|
|
482
|
-
|
|
483
|
-
```json
|
|
484
|
-
{
|
|
485
|
-
"functions": [
|
|
486
|
-
{
|
|
487
|
-
"name": "getValueFromSale",
|
|
488
|
-
"fn": "getValueFromSale(queries, target)",
|
|
489
|
-
"variables": {
|
|
490
|
-
"target": "createdAt",
|
|
491
|
-
"queries": [
|
|
492
|
-
{
|
|
493
|
-
"value": ":productType",
|
|
494
|
-
"label": "products.productType"
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
"value": ":applicant.documents[0].value",
|
|
498
|
-
"label": "applicant.documents.value"
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
"value": ":applicant.documents[0].type",
|
|
502
|
-
"label": "applicant.documents.type"
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
"value": "date(-5d)",
|
|
506
|
-
"label": "createdAt",
|
|
507
|
-
"op": "gte"
|
|
508
|
-
}
|
|
509
|
-
]
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
]
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### 🔄 Using the Function Output in Queries
|
|
517
|
-
|
|
518
|
-
To use the result of a function, reference it in a `query.value` using the syntax: `fn:functionName`.
|
|
519
|
-
|
|
520
|
-
json
|
|
521
|
-
|
|
522
|
-
CopyEdit
|
|
523
|
-
|
|
524
|
-
```json
|
|
525
|
-
{
|
|
526
|
-
"label": "createdAt",
|
|
527
|
-
"op": "range",
|
|
528
|
-
"value": {
|
|
529
|
-
"from": "fn:getValueFromSale",
|
|
530
|
-
"to": "date(0)"
|
|
531
|
-
},
|
|
532
|
-
"defaultValue": {
|
|
533
|
-
"from": "date(-5d)",
|
|
534
|
-
"to": "date(0)"
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
🔍 In this case, `getValueFromSale(...)` will run **before** the query is constructed, and the result will be injected into the `from` field.
|
|
540
|
-
|
|
541
|
-
---
|
|
542
|
-
|
|
543
|
-
### 🧩 How It Works
|
|
544
|
-
|
|
545
|
-
1. The engine scans all `functions` in `variables.functions`.
|
|
546
|
-
2. For each `query` entry in `variables.queries`, if `value` (or `from`, `to`) starts with `fn:`, it:
|
|
547
|
-
- Finds the corresponding `function` by name
|
|
548
|
-
- Resolves its `variables` (supporting nested `:context.paths`)
|
|
549
|
-
- Executes the declared function like `getValueFromSale(queries, target)`
|
|
550
|
-
|
|
551
|
-
3. The return value of the function replaces the `fn:` reference.
|
|
552
|
-
|
|
553
|
-
---
|
|
554
|
-
|
|
555
|
-
### ✅ Benefits
|
|
556
|
-
|
|
557
|
-
- Enables **pre-processing logic** in a declarative way
|
|
558
|
-
- Keeps rule/query definitions **clean and context-aware**
|
|
559
|
-
- Promotes **reuse** of custom logic across different rules or queries
|
|
560
|
-
- Provides **defaultValue** fallback in case of failure
|
|
561
|
-
|
|
562
|
-
---
|
|
563
|
-
|
|
564
|
-
> 💡 Tip: Functions must be registered and resolvable at runtime by your system. The evaluation engine must be able to map the function name (`getValueFromSale`) to a real implementation.
|
|
565
|
-
|
|
566
|
-
## 🧰 Requirements
|
|
567
|
-
|
|
568
|
-
- NestJS
|
|
569
|
-
|
|
570
|
-
- RuleEngineModule (compatible expression parser/executor)
|
|
571
|
-
|
|
572
|
-
- Custom service to inject context functions (like `RateLimitingInjectFunctionsUseCase`)
|
|
573
|
-
|
|
574
|
-
## 📎 Notes
|
|
575
|
-
|
|
576
|
-
- Rules should be defined and stored externally (e.g., DB, config).
|
|
577
|
-
|
|
578
|
-
- The context extractor paths must match the structure of your handler inputs (e.g., body, params).
|
|
579
|
-
|
|
580
|
-
- Ensure `getFunctions()` returns **async functions** that provide the required values for the expression.
|
|
581
|
-
|
|
582
|
-
## 🧾 License
|
|
583
|
-
|
|
584
|
-
MIT
|
package/dist/package.json
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@edirect/rate-limit-module",
|
|
3
|
-
"version": "11.0.65",
|
|
4
|
-
"main": "./dist/src/index.js",
|
|
5
|
-
"types": "./dist/src/index.d.ts",
|
|
6
|
-
"exports": {
|
|
7
|
-
".": {
|
|
8
|
-
"import": "./dist/src/index.js",
|
|
9
|
-
"default": "./dist/src/index.js",
|
|
10
|
-
"require": "./dist/src/index.js",
|
|
11
|
-
"types": "./dist/src/index.d.ts"
|
|
12
|
-
},
|
|
13
|
-
"./package.json": "./package.json"
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"@edirect/auth": "^11.0.65",
|
|
20
|
-
"@edirect/logger": "^11.0.65",
|
|
21
|
-
"@nestjs/cache-manager": "^3.1.3",
|
|
22
|
-
"@nestjs/common": "^11.2.1",
|
|
23
|
-
"@nestjs/config": "^4.0.4",
|
|
24
|
-
"@nestjs/core": "^11.2.1",
|
|
25
|
-
"@nestjs/mongoose": "^11.0.4",
|
|
26
|
-
"@nestjs/swagger": "^11.4.7",
|
|
27
|
-
"cache-manager-redis-store": "^3.0.1",
|
|
28
|
-
"class-transformer": "^0.5.1",
|
|
29
|
-
"class-validator": "^0.15.1",
|
|
30
|
-
"dayjs": "^1.11.23",
|
|
31
|
-
"ioredis": "^6.0.0",
|
|
32
|
-
"jexl": "^2.3.0",
|
|
33
|
-
"mongoose": "^9.9.3",
|
|
34
|
-
"tslib": "^2.8.1",
|
|
35
|
-
"uuid": "^14.0.1"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"@types/jexl": "^2.3.4"
|
|
39
|
-
},
|
|
40
|
-
"type": "commonjs"
|
|
41
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":"5.9.3"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|