npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@edirect/rate-limit-module

v2.0.10

Published

Readme

📈 Rate Limiting Module

This module provides 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.

✨ Features

  • Define rate-limiting rules with custom expressions.

  • Dynamically inject context-aware functions for rule evaluation.

  • Easily integrate with any resource via a decorator.

  • Built with extensibility and modularity in mind.

🧱 How It Works

  1. You define a rule using a simple expression (e.g., count_quote( ) > 1).

  2. The rule is evaluated in runtime using data injected from the request context.

  3. If the expression is true, the action is blocked.

🧩 Usage

1. Install the Module


@Module({

imports: [

RateLimitingModule,

RuleEngineModule.forFeatureAsync({

useFactory: async (quoteService:  QuoteService) => {

return  await quoteService.countQuote();

},

inject: [QuoteService],

}),

],

})

export  class  YourFeatureModule {}

2. Decorate Your Handlers

Use the @RateLimitingRuleMetadata decorator to attach metadata used during rule evaluation.


import { RateLimitingRuleMetadata } from  'path-to-rate-limiting-module';

  

@RateLimitingRuleMetadata({

action: 'create_quote',

partnerPath: 'partner.partnerId',

providerPath: 'provider',

productTypePath: 'productType',

marketPath: 'market',

})

@Post('quotes')

createQuote(@Body() body: CreateQuoteDto) {

return  this.quoteService.create(body);

}
  • action: Name of the action to be controlled.

  • partnerPath: Path to extract the partner ID from the request context.

  • providerPath: Path to extract the provider.

  • productTypePath: Path to extract the product type.

🛠 Rule Engine Integration

This module depends on an underlying rule engine to evaluate expressions. You must provide the functions used in expressions through:


RuleEngineModule.forFeatureAsync({

useFactory: async (useCase:  RateLimitingInjectFunctionsUseCase) => {

return  await useCase.getFunctions();

// returns an object like { count_quote: async () => number }

},

inject: [RateLimitingInjectFunctionsUseCase],

});

🧪 Example Rule

A rule stored in your system might look like:


{
	"expression": "count_quote() > 1",
	"action": "create_quote"
}

If the expression returns true, the rate limiter will block further create_quote actions for the user based on the context values.

🧠 Dynamic Parameter Resolution

When building queries based on request context, it's possible to reference nested values using paths. This system supports:

  • Resolving values using variable placeholders like :applicant.documents[0].value.

  • Interpreting date expressions safely, like date(-5d) (5 days ago), without using eval.

Supported Syntax


{
  "value": ":applicant.documents[0].value",
  "label": "applicant.documents.value"
}

Will extract the value from the context:

{
  "applicant": {
    "documents": [
      { "type": "NIF", "value": "ABCDE" }
    ]
  }
}

Result: "ABCDE"

You can also use dynamic dates:

{
  "value": "date(-7d)",
  "label": "createdAt",
  "op": "gte"
}

Result: "2025-07-10T00:00:00.000Z" (7 days ago)

Supported Date Units

  • d: Days (default)

  • M: Months

  • y: Years

Examples:

  • date(-5d) → 5 days ago

  • date(-1M) → 1 month ago

  • date(0) → Today

No dynamic code execution (eval, new Function) is used — expressions are parsed securely.

⚙️ Supported Operators (op)

You can optionally define an op (operation) field to specify how the value should be interpreted. The system currently supports:

gte — Greater Than or Equal (usually for dates)

{
  "value": "date(-7d)",
  "label": "createdAt",
  "op": "gte"
}

🔍 This means: only include records created after 7 days ago.

{
  "value": {
    "from": 1,
    "to": 10
  },
  "label": "createdAt",
  "op": "range"
}

🔍 This sets a static date or value range. Can be used for numbers, dates, or custom fields.

rangeDate — Dynamic Date Range

{
  "value": {
    "from": "date(-30d)",
    "to": "date(0)"
  },
  "label": "createdAt",
  "op": "rangeDate"
}

🔍 This automatically resolves both from and to values as dates. For example, this would generate a date range from 30 days ago to now.

🧬 DSL Support and MongoDB Integration

This module uses a lightweight DSL (Domain-Specific Language) to define rules and filters in a declarative and expressive way.

What is a DSL?

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:

  • Conditional expressions (e.g., count_quote() > 1)
  • Filter structures using dynamic context variables and operators (eq, range, gte, etc.)

These DSL expressions are not executed as raw code, but safely parsed and evaluated using resolvers and expression evaluators.


DSL to MongoDB Filter Conversion

One powerful feature of this module is the ability to transform DSL filters into MongoDB-compatible query objects.

For example, the following DSL input:

{
  createdAt: {
    $gte: new Date('2025-06-17T00:00:00.000Z'),
    $lte: new Date('2025-07-17T00:00:00.000Z')
  }
}

This is handled by utility functions like buildDlsOp, which map DSL constructs to database query.

✅ Benefits of Using DSL

  • Separation of logic from code: Business rules can be stored in DB or configs

  • Extensibility: Easy to support new operators or DB backends (e.g., SQL, Elasticsearch)

  • Security: No raw eval or dynamic JS execution

  • Readability: DSL is expressive for non-technical stakeholders

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.

🧾 Dynamic Query Context with variables.queries

The variables object is designed to allow structured dynamic query generation through an array of queries. Each query defines:

  • The function(s) where this context will be injected (fnContext)
  • The parameters to be resolved and passed (query)
  • Optional operations and default values

Structure of variables.queries

{
  "queries": [
    {
      "fnContext": ["countQuoteByParams"],
      "query": [
        {
          "value": ":partner.partnerId",
          "label": "partner.partnerId"
        },
        {
          "value": ":productType",
          "label": "productType"
        },
        {
          "value": ":provider",
          "label": "provider"
        },
        {
          "value": ":applicant.documents[0].value",
          "label": "applicant.documents.value"
        },
        {
          "value": ":applicant.documents[0].type",
          "label": "applicant.documents.type"
        },
        {
          "label": "createdAt",
          "op": "range",
          "value": {
            "to": "date(0)",
            "from": "fn:getValueFromSale"
          },
          "defaultValue": {
            "from": "date(-5d)",
            "to": "date(0)"
          }
        }
      ]
    }
  ]
}

🔍 Explanation

Field

Description

fnContext

The name(s) of the function(s) that will receive this query as parameter input

query

Array of parameter definitions. Each will be resolved based on context or DSL expressions

label

The target field to be passed (e.g. used to generate a MongoDB query or as input to a service)

value

Can be a dynamic reference (e.g., :context.path), a static value, or a DSL expression

op

Optional. Defines the operator (eq, range, rangeDate, gte, etc.)

defaultValue

Fallback value(s) to be used if dynamic resolution fails (e.g., missing path or function)


🧠 Special Value Formats

  • :path.to.value — Dynamically extracted from the context object

  • date(-7d) — Resolved as a safe date expression

  • fn:getValueFromSale — Calls a predefined function to resolve the value dynamically

  • Static values like "moneyhero" are passed as-is

    🧪 Use Case Example

Imagine your service defines a method like:

async countQuoteByParams(filters: Record<string, any>): Promise<number>

At runtime, the module will:

  1. Resolve each value in the query array using context and supported expressions.

  2. Construct a query object:

{
  partner: { partnerId: "bolttech" },
  productType: "car-insurance",
  provider: "bolttech",
  applicant: {
    documents: [{ type: "NIF", value: "ABCDE" }]
  },
  createdAt: {
    from: new Date("2025-07-25T00:00:00Z"), // resolved from fn:getValueFromSale
    to: new Date("2025-07-30T00:00:00Z")    // resolved from date(0)
  }
}
  1. Inject this into countQuoteByParams(...) This approach gives you a highly flexible, configurable system for controlling dynamic function parameters via context-based DSLs and runtime evaluation.

🧠 Custom Runtime Functions with variables.functions

In addition to defining query parameters in variables.queries, the system also supports custom runtime functions through the variables.functions array.

These functions can be executed before query construction to compute values that are injected dynamically into query fields.


🔧 Structure of functions

Each function definition includes:

| Field | Description | |------------|-----------------------------------------------------------------------------| | name | Unique function identifier | | fn | The function to execute, in the format functionName(args...) | | variables| Parameters passed to the function, which can include subqueries and values |

📌 Example

{
  "functions": [
    {
      "name": "getValueFromSale",
      "fn": "getValueFromSale(queries, target)",
      "variables": {
        "target": "createdAt",
        "queries": [
          {
            "value": ":productType",
            "label": "products.productType"
          },
          {
            "value": ":applicant.documents[0].value",
            "label": "applicant.documents.value"
          },
          {
            "value": ":applicant.documents[0].type",
            "label": "applicant.documents.type"
          },
          {
            "value": "date(-5d)",
            "label": "createdAt",
            "op": "gte"
          }
        ]
      }
    }
  ]
}

🔄 Using the Function Output in Queries

To use the result of a function, reference it in a query.value using the syntax: fn:functionName.

json

CopyEdit

{
   "label":"createdAt",
   "op":"range",
   "value":{
      "from":"fn:getValueFromSale",
      "to":"date(0)"
   },
   "defaultValue":{
      "from":"date(-5d)",
      "to":"date(0)"
   }
}

🔍 In this case, getValueFromSale(...) will run before the query is constructed, and the result will be injected into the from field.


🧩 How It Works

  1. The engine scans all functions in variables.functions.

  2. For each query entry in variables.queries, if value (or from, to) starts with fn:, it:

    • Finds the corresponding function by name

    • Resolves its variables (supporting nested :context.paths)

    • Executes the declared function like getValueFromSale(queries, target)

  3. The return value of the function replaces the fn: reference.


✅ Benefits

  • Enables pre-processing logic in a declarative way

  • Keeps rule/query definitions clean and context-aware

  • Promotes reuse of custom logic across different rules or queries

  • Provides defaultValue fallback in case of failure


💡 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.

🧰 Requirements

  • NestJS

  • RuleEngineModule (compatible expression parser/executor)

  • Custom service to inject context functions (like RateLimitingInjectFunctionsUseCase)

📎 Notes

  • Rules should be defined and stored externally (e.g., DB, config).

  • The context extractor paths must match the structure of your handler inputs (e.g., body, params).

  • Ensure getFunctions() returns async functions that provide the required values for the expression.

🧾 License

MIT