English
Meteor's DDPRateLimiter.addRule() works, but it creates side effects scattered across your codebase. You never know where a limit is defined or what the threshold is.
This mixin lets you declare rate limits right where you define the method — explicit, colocated, easy to audit.
# From npmjs.com
npm install @allohouston/ddp-rate-limiter-mixin
# Or from GitHub Packages (add to .npmrc: @allohouston:registry=https://npm.pkg.github.com)
npm install @allohouston/ddp-rate-limiter-mixin --registry=https://npm.pkg.github.comimport { ValidatedMethod } from "meteor/mdg:validated-method";
import { RateLimiterMixin } from "@allohouston/ddp-rate-limiter-mixin";
const sendMessage = new ValidatedMethod({
name: "chat.sendMessage",
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000, // 5 requests per 5 seconds
},
validate: null,
async run({ text, channelId }) {
// your method logic
},
});That's it. 5 requests per 5 seconds, for all clients, enforced server-side.
const updateProfile = new ValidatedMethod({
name: "users.updateProfile",
mixins: [RateLimiterMixin],
rateLimit: {
matcher: { userId: "specificUserId" },
numRequests: 3,
timeInterval: 10000,
},
// ...
});const deletePost = new ValidatedMethod({
name: "posts.delete",
mixins: [RateLimiterMixin],
rateLimit: {
matcher: {
userId(userId) {
// Only rate-limit non-admin users
return userId !== "adminId";
},
},
numRequests: 2,
timeInterval: 60000, // 2 deletions per minute
},
// ...
});const submitForm = new ValidatedMethod({
name: "forms.submit",
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 3,
timeInterval: 60000,
errorMessage: (data) =>
`Too many submissions. Try again in ${Math.ceil(data.timeToReset / 1000)}s.`,
},
// ...
});A mixin function for ValidatedMethod. Registers a DDPRateLimiter.addRule() on the server and returns the options with an added rateLimitRuleId.
On the client, returns methodOptions unchanged.
| Property | Type | Required | Description |
|---|---|---|---|
numRequests |
number |
yes | Max requests per interval (must be >= 1) |
timeInterval |
number |
yes | Interval in ms (must be > 0) |
matcher |
object |
no | Filter which requests count towards the limit |
callback |
function |
no | Called after rule evaluation |
errorMessage |
string | function |
no | Custom error when limit is exceeded |
All optional. Unspecified fields match all requests.
| Property | Type | Description |
|---|---|---|
userId |
string | (id: string) => boolean |
Match by user ID |
connectionId |
string | (id: string) => boolean |
Match by DDP connection |
clientAddress |
string | (addr: string) => boolean |
Match by IP address |
nameis always the method name,typeis always"method".
// reply
{
allowed: boolean; // was the call allowed?
timeToReset: number; // ms until rate limit resets
numInvocationsLeft: number; // remaining calls in this interval
}
// ruleInput
{
type: string; // "method" or "subscription"
name: string; // method name
userId: string; // user ID
connectionId: string; // DDP connection ID
clientAddress: string; // client IP
}Custom error message when the rate limit is exceeded. Can be a static string or a function receiving { timeToReset } that returns a string.
Uses DDPRateLimiter.setErrorMessageOnRule() (Meteor 3+). Silently ignored on older Meteor versions.
After the mixin runs, methodOptions.rateLimitRuleId contains the rule ID returned by DDPRateLimiter.addRule(). Use it with DDPRateLimiter.removeRule() or DDPRateLimiter.setErrorMessageOnRule() if needed.
Full type definitions included.
import { RateLimiterMixin } from "@allohouston/ddp-rate-limiter-mixin";
import type {
MethodOptions,
RateLimitConfig,
RateLimitMatcher,
RateLimitReply,
RateLimitInput,
} from "@allohouston/ddp-rate-limiter-mixin";v2 is a breaking change:
| Change | v1 | v2 |
|---|---|---|
| Language | JavaScript (Babel 6) | TypeScript (strict) |
| Module format | CJS only | ESM + CJS (dual) |
| Runtime deps | babel-runtime |
0 |
| Mutability | Mutates methodOptions |
Returns new object |
rateLimitRuleId |
Not exposed | Exposed in returned options |
errorMessage |
Not supported | Supported (string or function) |
| Input validation | Basic type checks | Strict (NaN, Infinity, null, arrays) |
| Node.js | Any | >= 20 |
| Meteor | 1.x - 2.x | 3.4+ |
Français
Le DDPRateLimiter.addRule() de Meteor fonctionne, mais il crée des effets de bord dispersés dans le code. On ne sait jamais où une limite est définie ni quel est le seuil.
Ce mixin permet de déclarer les limites directement dans la définition de la méthode — explicite, colocalisé, facile à auditer.
# Depuis npmjs.com
npm install @allohouston/ddp-rate-limiter-mixin
# Ou depuis GitHub Packages (ajouter dans .npmrc : @allohouston:registry=https://npm.pkg.github.com)
npm install @allohouston/ddp-rate-limiter-mixin --registry=https://npm.pkg.github.comimport { ValidatedMethod } from "meteor/mdg:validated-method";
import { RateLimiterMixin } from "@allohouston/ddp-rate-limiter-mixin";
const sendMessage = new ValidatedMethod({
name: "chat.sendMessage",
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000, // 5 requêtes par 5 secondes
},
validate: null,
async run({ text, channelId }) {
// votre logique
},
});C'est tout. 5 requêtes par 5 secondes, pour tous les clients, appliqué côté serveur.
const updateProfile = new ValidatedMethod({
name: "users.updateProfile",
mixins: [RateLimiterMixin],
rateLimit: {
matcher: { userId: "specificUserId" },
numRequests: 3,
timeInterval: 10000,
},
// ...
});const deletePost = new ValidatedMethod({
name: "posts.delete",
mixins: [RateLimiterMixin],
rateLimit: {
matcher: {
userId(userId) {
// Limiter uniquement les utilisateurs non-admin
return userId !== "adminId";
},
},
numRequests: 2,
timeInterval: 60000, // 2 suppressions par minute
},
// ...
});const submitForm = new ValidatedMethod({
name: "forms.submit",
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 3,
timeInterval: 60000,
errorMessage: (data) =>
`Trop de soumissions. Réessayez dans ${Math.ceil(data.timeToReset / 1000)}s.`,
},
// ...
});Fonction mixin pour ValidatedMethod. Enregistre une règle DDPRateLimiter.addRule() côté serveur et retourne les options avec un rateLimitRuleId ajouté.
Côté client, retourne methodOptions sans modification.
| Propriété | Type | Requis | Description |
|---|---|---|---|
numRequests |
number |
oui | Requêtes max par intervalle (doit être >= 1) |
timeInterval |
number |
oui | Intervalle en ms (doit être > 0) |
matcher |
object |
non | Filtre les requêtes à comptabiliser |
callback |
function |
non | Appelée après évaluation de la règle |
errorMessage |
string | function |
non | Message d'erreur quand la limite est atteinte |
Toutes optionnelles. Les champs absents matchent toutes les requêtes.
| Propriété | Type | Description |
|---|---|---|
userId |
string | (id: string) => boolean |
Filtrer par ID utilisateur |
connectionId |
string | (id: string) => boolean |
Filtrer par connexion DDP |
clientAddress |
string | (addr: string) => boolean |
Filtrer par adresse IP |
nameest toujours le nom de la méthode,typeest toujours"method".
// reply
{
allowed: boolean; // l'appel est-il autorisé ?
timeToReset: number; // ms avant réinitialisation de la limite
numInvocationsLeft: number; // appels restants dans l'intervalle
}
// ruleInput
{
type: string; // "method" ou "subscription"
name: string; // nom de la méthode
userId: string; // ID utilisateur
connectionId: string; // ID de connexion DDP
clientAddress: string; // IP du client
}Message d'erreur personnalisé quand la limite est dépassée. Peut être une chaîne statique ou une fonction recevant { timeToReset } et retournant une chaîne.
Utilise DDPRateLimiter.setErrorMessageOnRule() (Meteor 3+). Silencieusement ignoré sur les anciennes versions de Meteor.
Après exécution du mixin, methodOptions.rateLimitRuleId contient l'ID de la règle retourné par DDPRateLimiter.addRule(). Utilisable avec DDPRateLimiter.removeRule() ou DDPRateLimiter.setErrorMessageOnRule().
Définitions de types complètes incluses.
import { RateLimiterMixin } from "@allohouston/ddp-rate-limiter-mixin";
import type {
MethodOptions,
RateLimitConfig,
RateLimitMatcher,
RateLimitReply,
RateLimitInput,
} from "@allohouston/ddp-rate-limiter-mixin";v2 est un breaking change :
| Changement | v1 | v2 |
|---|---|---|
| Langage | JavaScript (Babel 6) | TypeScript (strict) |
| Format module | CJS uniquement | ESM + CJS (dual) |
| Deps runtime | babel-runtime |
0 |
| Mutabilité | Mute methodOptions |
Retourne un nouvel objet |
rateLimitRuleId |
Non exposé | Exposé dans les options retournées |
errorMessage |
Non supporté | Supporté (string ou function) |
| Validation | Vérification de type basique | Stricte (NaN, Infinity, null, arrays) |
| Node.js | Tout | >= 20 |
| Meteor | 1.x - 2.x | 3.4+ |