{"_id":"@allohouston/ddp-rate-limiter-mixin","_rev":"2-736da052befb33fe23c4f12ebbf4ef7e","name":"@allohouston/ddp-rate-limiter-mixin","dist-tags":{"latest":"2.0.1"},"versions":{"2.0.0":{"name":"@allohouston/ddp-rate-limiter-mixin","version":"2.0.0","keywords":["meteor","ddp","rate-limiter","validated-method","mixin"],"license":"MIT","_id":"@allohouston/ddp-rate-limiter-mixin@2.0.0","maintainers":[{"name":"barodrig","email":"brodriguez@allohouston.fr"},{"name":"gmacherey","email":"gmacherey@allohouston.fr"}],"homepage":"https://github.com/allohouston/ddp-rate-limiter-mixin#readme","bugs":{"url":"https://github.com/allohouston/ddp-rate-limiter-mixin/issues"},"dist":{"shasum":"c89ed693810ba5319c63ed28257df6aedd89e34c","tarball":"https://registry.npmjs.org/@allohouston/ddp-rate-limiter-mixin/-/ddp-rate-limiter-mixin-2.0.0.tgz","fileCount":11,"integrity":"sha512-ITNpTcUJGgA/aANml8UiUTyp6NcTbunBkB0MEtksHNGJeqpl+4n9v/ZmyaqVanuJjj0xWJpyWQMV1R9p6qLP6g==","signatures":[{"sig":"MEUCIQDO/fqhnZRtfS8rL7JM2nqqXRkSju5DDr3uOHmJRKpHRAIgDS4ZWMlyBeG7Z2avmH5JsDcffQlFH8ZoUhAdd8bpYOw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35824},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.mts","module":"./dist/index.mjs","engines":{"node":">=20.0.0"},"exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"gitHead":"ba35d79ddbea69d9a621650e7b2b6dbe1d09fa15","scripts":{"lint":"biome check .","test":"vitest run","build":"tsdown","format":"biome format --write .","prepare":"husky && npm run build","lint:fix":"biome check --fix .","test:watch":"vitest watch","format:check":"biome format .","test:coverage":"vitest run --coverage","prepublishOnly":"npm run lint && npm run test && npm run build"},"_npmUser":{"name":"barodrig","email":"brodriguez@allohouston.fr"},"repository":{"url":"git+https://github.com/allohouston/ddp-rate-limiter-mixin.git","type":"git"},"_npmVersion":"10.8.2","description":"A mixin for mdg:validated-method to add rate limitation support to Meteor methods.","directories":{},"_nodeVersion":"20.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","tsdown":"^0.16.5","vitest":"^4.0.9","typescript":"^5.9.3","@types/meteor":"^2.9.10","@biomejs/biome":"^2.3.7","@commitlint/cli":"^19.8.0","@vitest/coverage-v8":"^4.0.13","@semantic-release/npm":"^12.0.2","@semantic-release/github":"^11.0.6","@commitlint/config-conventional":"^19.8.0","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/release-notes-generator":"^14.1.0","conventional-changelog-conventionalcommits":"^8.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ddp-rate-limiter-mixin_2.0.0_1773758840856_0.3304401980807554","host":"s3://npm-registry-packages-npm-production"}},"2.0.1":{"name":"@allohouston/ddp-rate-limiter-mixin","version":"2.0.1","description":"A mixin for mdg:validated-method to add rate limitation support to Meteor methods.","repository":{"type":"git","url":"git+https://github.com/allohouston/ddp-rate-limiter-mixin.git"},"license":"MIT","type":"module","main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.mts","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./package.json":"./package.json"},"engines":{"node":">=20.0.0"},"scripts":{"build":"tsdown","lint":"biome check .","lint:fix":"biome check --fix .","format":"biome format --write .","format:check":"biome format .","test":"vitest run","test:coverage":"vitest run --coverage","test:watch":"vitest watch","prepare":"husky && npm run build","prepublishOnly":"npm run lint && npm run test && npm run build"},"devDependencies":{"@biomejs/biome":"^2.3.7","@commitlint/cli":"^19.8.0","@commitlint/config-conventional":"^19.8.0","@semantic-release/commit-analyzer":"^13.0.1","@semantic-release/github":"^11.0.6","@semantic-release/npm":"^12.0.2","@semantic-release/release-notes-generator":"^14.1.0","@types/meteor":"^2.9.10","@vitest/coverage-v8":"^4.0.13","conventional-changelog-conventionalcommits":"^8.0.0","husky":"^9.1.7","tsdown":"^0.16.5","typescript":"^5.9.3","vitest":"^4.0.9"},"publishConfig":{"access":"public"},"keywords":["meteor","ddp","rate-limiter","validated-method","mixin"],"gitHead":"bf7cd1ae94a3f92e6068b467d2a22951137ffeb1","_id":"@allohouston/ddp-rate-limiter-mixin@2.0.1","bugs":{"url":"https://github.com/allohouston/ddp-rate-limiter-mixin/issues"},"homepage":"https://github.com/allohouston/ddp-rate-limiter-mixin#readme","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-CJ7B6tx/uQtwN4b7b/8ffUpfDWsjRAxuUUpiZesqsSZhqVPrAQBdICLXQl4nFWA6wfpGozaG0TIy292ivYHJlA==","shasum":"e66b13545f314582510095e121708dcacea7c599","tarball":"https://registry.npmjs.org/@allohouston/ddp-rate-limiter-mixin/-/ddp-rate-limiter-mixin-2.0.1.tgz","fileCount":11,"unpackedSize":35824,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@allohouston%2fddp-rate-limiter-mixin@2.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIC2br9fGPyl1STMyaiNdPxg8q0C+hXeDNs4gJpNpHjZKAiEAhZzSJ/hgWS8Ym+fA2BqUxAZkHJdnC+HIad+uN9duzRY="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:31f9a3d3-2b4b-4803-8851-a2a66b27b7fe"}},"directories":{},"maintainers":[{"name":"barodrig","email":"brodriguez@allohouston.fr"},{"name":"gmacherey","email":"gmacherey@allohouston.fr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ddp-rate-limiter-mixin_2.0.1_1773759353760_0.6369852844432735"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-17T14:47:20.779Z","modified":"2026-03-17T14:55:54.286Z","2.0.0":"2026-03-17T14:47:20.984Z","2.0.1":"2026-03-17T14:55:53.911Z"},"bugs":{"url":"https://github.com/allohouston/ddp-rate-limiter-mixin/issues"},"license":"MIT","homepage":"https://github.com/allohouston/ddp-rate-limiter-mixin#readme","keywords":["meteor","ddp","rate-limiter","validated-method","mixin"],"repository":{"type":"git","url":"git+https://github.com/allohouston/ddp-rate-limiter-mixin.git"},"description":"A mixin for mdg:validated-method to add rate limitation support to Meteor methods.","maintainers":[{"name":"barodrig","email":"brodriguez@allohouston.fr"},{"name":"gmacherey","email":"gmacherey@allohouston.fr"}],"readme":"<p align=\"center\">\n  <h1 align=\"center\">@allohouston/ddp-rate-limiter-mixin</h1>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/allohouston/ddp-rate-limiter-mixin/actions/workflows/ci.yml\"><img src=\"https://github.com/allohouston/ddp-rate-limiter-mixin/actions/workflows/ci.yml/badge.svg\" alt=\"CI\" /></a>\n  <a href=\"https://www.npmjs.com/package/@allohouston/ddp-rate-limiter-mixin\"><img src=\"https://img.shields.io/npm/v/@allohouston/ddp-rate-limiter-mixin\" alt=\"npm version\" /></a>\n  <a href=\"https://github.com/allohouston/ddp-rate-limiter-mixin/pkgs/npm/ddp-rate-limiter-mixin\"><img src=\"https://img.shields.io/github/v/release/allohouston/ddp-rate-limiter-mixin?label=github%20packages\" alt=\"GitHub Packages version\" /></a>\n  <img src=\"https://img.shields.io/badge/coverage-100%25-brightgreen\" alt=\"coverage 100%\" />\n  <img src=\"https://img.shields.io/badge/meteor-3.4%2B-blue\" alt=\"Meteor 3.4+\" />\n  <img src=\"https://img.shields.io/badge/node-%3E%3D20-green\" alt=\"Node >= 20\" />\n  <a href=\"https://github.com/allohouston/ddp-rate-limiter-mixin/blob/master/LICENSE\"><img src=\"https://img.shields.io/github/license/allohouston/ddp-rate-limiter-mixin\" alt=\"license\" /></a>\n</p>\n\n<p align=\"center\">\n  <img src=\"https://img.shields.io/badge/TypeScript-strict-blue?logo=typescript&logoColor=white\" alt=\"TypeScript\" />\n  <img src=\"https://img.shields.io/badge/lint-Biome-60a5fa?logo=biome&logoColor=white\" alt=\"Biome\" />\n  <img src=\"https://img.shields.io/badge/test-Vitest-6e9f18?logo=vitest&logoColor=white\" alt=\"Vitest\" />\n  <img src=\"https://img.shields.io/badge/0%20runtime%20deps-brightgreen\" alt=\"zero dependencies\" />\n</p>\n\n---\n\n<details open>\n<summary><b>English</b></summary>\n\n## Why?\n\nMeteor'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.\n\nThis mixin lets you declare rate limits **right where you define the method** — explicit, colocated, easy to audit.\n\n## Install\n\n```bash\nmeteor add ddp-rate-limiter\n\n# From npmjs.com\nnpm install @allohouston/ddp-rate-limiter-mixin\n\n# Or from GitHub Packages (add to .npmrc: @allohouston:registry=https://npm.pkg.github.com)\nnpm install @allohouston/ddp-rate-limiter-mixin --registry=https://npm.pkg.github.com\n```\n\n## Quick Start\n\n```typescript\nimport { ValidatedMethod } from \"meteor/mdg:validated-method\";\nimport { RateLimiterMixin } from \"@allohouston/ddp-rate-limiter-mixin\";\n\nconst sendMessage = new ValidatedMethod({\n    name: \"chat.sendMessage\",\n    mixins: [RateLimiterMixin],\n    rateLimit: {\n        numRequests: 5,\n        timeInterval: 5000, // 5 requests per 5 seconds\n    },\n    validate: null,\n    async run({ text, channelId }) {\n        // your method logic\n    },\n});\n```\n\nThat's it. 5 requests per 5 seconds, for all clients, enforced server-side.\n\n## Examples\n\n### Limit a specific user\n\n```typescript\nconst updateProfile = new ValidatedMethod({\n    name: \"users.updateProfile\",\n    mixins: [RateLimiterMixin],\n    rateLimit: {\n        matcher: { userId: \"specificUserId\" },\n        numRequests: 3,\n        timeInterval: 10000,\n    },\n    // ...\n});\n```\n\n### Custom matcher function\n\n```typescript\nconst deletePost = new ValidatedMethod({\n    name: \"posts.delete\",\n    mixins: [RateLimiterMixin],\n    rateLimit: {\n        matcher: {\n            userId(userId) {\n                // Only rate-limit non-admin users\n                return userId !== \"adminId\";\n            },\n        },\n        numRequests: 2,\n        timeInterval: 60000, // 2 deletions per minute\n    },\n    // ...\n});\n```\n\n### Custom error message\n\n```typescript\nconst submitForm = new ValidatedMethod({\n    name: \"forms.submit\",\n    mixins: [RateLimiterMixin],\n    rateLimit: {\n        numRequests: 3,\n        timeInterval: 60000,\n        errorMessage: (data) =>\n            `Too many submissions. Try again in ${Math.ceil(data.timeToReset / 1000)}s.`,\n    },\n    // ...\n});\n```\n\n## API Reference\n\n### `RateLimiterMixin(methodOptions) → methodOptions`\n\nA mixin function for `ValidatedMethod`. Registers a `DDPRateLimiter.addRule()` on the server and returns the options with an added `rateLimitRuleId`.\n\nOn the client, returns `methodOptions` unchanged.\n\n### `rateLimit` options\n\n| Property | Type | Required | Description |\n|----------|------|:--------:|-------------|\n| `numRequests` | `number` | **yes** | Max requests per interval (must be >= 1) |\n| `timeInterval` | `number` | **yes** | Interval in ms (must be > 0) |\n| `matcher` | `object` | no | Filter which requests count towards the limit |\n| `callback` | `function` | no | Called after rule evaluation |\n| `errorMessage` | `string \\| function` | no | Custom error when limit is exceeded |\n\n### `matcher` properties\n\nAll optional. Unspecified fields match all requests.\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `userId` | `string \\| (id: string) => boolean` | Match by user ID |\n| `connectionId` | `string \\| (id: string) => boolean` | Match by DDP connection |\n| `clientAddress` | `string \\| (addr: string) => boolean` | Match by IP address |\n\n> `name` is always the method name, `type` is always `\"method\"`.\n\n### `callback(reply, ruleInput)`\n\n```typescript\n// reply\n{\n    allowed: boolean;           // was the call allowed?\n    timeToReset: number;        // ms until rate limit resets\n    numInvocationsLeft: number; // remaining calls in this interval\n}\n\n// ruleInput\n{\n    type: string;           // \"method\" or \"subscription\"\n    name: string;           // method name\n    userId: string;         // user ID\n    connectionId: string;   // DDP connection ID\n    clientAddress: string;  // client IP\n}\n```\n\n### `errorMessage`\n\nCustom error message when the rate limit is exceeded. Can be a static string or a function receiving `{ timeToReset }` that returns a string.\n\nUses `DDPRateLimiter.setErrorMessageOnRule()` (Meteor 3+). Silently ignored on older Meteor versions.\n\n### `rateLimitRuleId`\n\nAfter the mixin runs, `methodOptions.rateLimitRuleId` contains the rule ID returned by `DDPRateLimiter.addRule()`. Use it with `DDPRateLimiter.removeRule()` or `DDPRateLimiter.setErrorMessageOnRule()` if needed.\n\n## TypeScript\n\nFull type definitions included.\n\n```typescript\nimport { RateLimiterMixin } from \"@allohouston/ddp-rate-limiter-mixin\";\nimport type {\n    MethodOptions,\n    RateLimitConfig,\n    RateLimitMatcher,\n    RateLimitReply,\n    RateLimitInput,\n} from \"@allohouston/ddp-rate-limiter-mixin\";\n```\n\n## Migration from v1\n\nv2 is a **breaking change**:\n\n| Change | v1 | v2 |\n|--------|----|----|\n| Language | JavaScript (Babel 6) | TypeScript (strict) |\n| Module format | CJS only | ESM + CJS (dual) |\n| Runtime deps | `babel-runtime` | **0** |\n| Mutability | Mutates `methodOptions` | Returns **new object** |\n| `rateLimitRuleId` | Not exposed | Exposed in returned options |\n| `errorMessage` | Not supported | Supported (string or function) |\n| Input validation | Basic type checks | Strict (NaN, Infinity, null, arrays) |\n| Node.js | Any | **>= 20** |\n| Meteor | 1.x - 2.x | **3.4+** |\n\n## Links\n\n- [Meteor DDPRateLimiter docs](https://docs.meteor.com/api/DDPRateLimiter)\n- [mdg:validated-method](https://github.com/meteor/validated-method)\n- [GitHub Packages](https://github.com/allohouston/ddp-rate-limiter-mixin/pkgs/npm/ddp-rate-limiter-mixin)\n\n</details>\n\n---\n\n<details>\n<summary><b>Fran&ccedil;ais</b></summary>\n\n## Pourquoi ?\n\nLe `DDPRateLimiter.addRule()` de Meteor fonctionne, mais il cree des **effets de bord** disperses dans le code. On ne sait jamais ou une limite est definie ni quel est le seuil.\n\nCe mixin permet de declarer les limites **directement dans la definition de la methode** — explicite, colocalise, facile a auditer.\n\n## Installation\n\n```bash\nmeteor add ddp-rate-limiter\n\n# Depuis npmjs.com\nnpm install @allohouston/ddp-rate-limiter-mixin\n\n# Ou depuis GitHub Packages (ajouter dans .npmrc : @allohouston:registry=https://npm.pkg.github.com)\nnpm install @allohouston/ddp-rate-limiter-mixin --registry=https://npm.pkg.github.com\n```\n\n## Demarrage rapide\n\n```typescript\nimport { ValidatedMethod } from \"meteor/mdg:validated-method\";\nimport { RateLimiterMixin } from \"@allohouston/ddp-rate-limiter-mixin\";\n\nconst sendMessage = new ValidatedMethod({\n    name: \"chat.sendMessage\",\n    mixins: [RateLimiterMixin],\n    rateLimit: {\n        numRequests: 5,\n        timeInterval: 5000, // 5 requetes par 5 secondes\n    },\n    validate: null,\n    async run({ text, channelId }) {\n        // votre logique\n    },\n});\n```\n\nC'est tout. 5 requetes par 5 secondes, pour tous les clients, applique cote serveur.\n\n## Exemples\n\n### Limiter un utilisateur specifique\n\n```typescript\nconst updateProfile = new ValidatedMethod({\n    name: \"users.updateProfile\",\n    mixins: [RateLimiterMixin],\n    rateLimit: {\n        matcher: { userId: \"specificUserId\" },\n        numRequests: 3,\n        timeInterval: 10000,\n    },\n    // ...\n});\n```\n\n### Matcher personnalise\n\n```typescript\nconst deletePost = new ValidatedMethod({\n    name: \"posts.delete\",\n    mixins: [RateLimiterMixin],\n    rateLimit: {\n        matcher: {\n            userId(userId) {\n                // Limiter uniquement les utilisateurs non-admin\n                return userId !== \"adminId\";\n            },\n        },\n        numRequests: 2,\n        timeInterval: 60000, // 2 suppressions par minute\n    },\n    // ...\n});\n```\n\n### Message d'erreur personnalise\n\n```typescript\nconst submitForm = new ValidatedMethod({\n    name: \"forms.submit\",\n    mixins: [RateLimiterMixin],\n    rateLimit: {\n        numRequests: 3,\n        timeInterval: 60000,\n        errorMessage: (data) =>\n            `Trop de soumissions. Reessayez dans ${Math.ceil(data.timeToReset / 1000)}s.`,\n    },\n    // ...\n});\n```\n\n## Reference API\n\n### `RateLimiterMixin(methodOptions) → methodOptions`\n\nFonction mixin pour `ValidatedMethod`. Enregistre une regle `DDPRateLimiter.addRule()` cote serveur et retourne les options avec un `rateLimitRuleId` ajoute.\n\nCote client, retourne `methodOptions` sans modification.\n\n### Options `rateLimit`\n\n| Propriete | Type | Requis | Description |\n|-----------|------|:------:|-------------|\n| `numRequests` | `number` | **oui** | Requetes max par intervalle (doit etre >= 1) |\n| `timeInterval` | `number` | **oui** | Intervalle en ms (doit etre > 0) |\n| `matcher` | `object` | non | Filtre les requetes a comptabiliser |\n| `callback` | `function` | non | Appelee apres evaluation de la regle |\n| `errorMessage` | `string \\| function` | non | Message d'erreur quand la limite est atteinte |\n\n### Proprietes du `matcher`\n\nToutes optionnelles. Les champs absents matchent toutes les requetes.\n\n| Propriete | Type | Description |\n|-----------|------|-------------|\n| `userId` | `string \\| (id: string) => boolean` | Filtrer par ID utilisateur |\n| `connectionId` | `string \\| (id: string) => boolean` | Filtrer par connexion DDP |\n| `clientAddress` | `string \\| (addr: string) => boolean` | Filtrer par adresse IP |\n\n> `name` est toujours le nom de la methode, `type` est toujours `\"method\"`.\n\n### `callback(reply, ruleInput)`\n\n```typescript\n// reply\n{\n    allowed: boolean;           // l'appel est-il autorise ?\n    timeToReset: number;        // ms avant reinitialisation de la limite\n    numInvocationsLeft: number; // appels restants dans l'intervalle\n}\n\n// ruleInput\n{\n    type: string;           // \"method\" ou \"subscription\"\n    name: string;           // nom de la methode\n    userId: string;         // ID utilisateur\n    connectionId: string;   // ID de connexion DDP\n    clientAddress: string;  // IP du client\n}\n```\n\n### `errorMessage`\n\nMessage d'erreur personnalise quand la limite est depassee. Peut etre une chaine statique ou une fonction recevant `{ timeToReset }` et retournant une chaine.\n\nUtilise `DDPRateLimiter.setErrorMessageOnRule()` (Meteor 3+). Silencieusement ignore sur les anciennes versions de Meteor.\n\n### `rateLimitRuleId`\n\nApres execution du mixin, `methodOptions.rateLimitRuleId` contient l'ID de la regle retourne par `DDPRateLimiter.addRule()`. Utilisable avec `DDPRateLimiter.removeRule()` ou `DDPRateLimiter.setErrorMessageOnRule()`.\n\n## TypeScript\n\nDefinitions de types completes incluses.\n\n```typescript\nimport { RateLimiterMixin } from \"@allohouston/ddp-rate-limiter-mixin\";\nimport type {\n    MethodOptions,\n    RateLimitConfig,\n    RateLimitMatcher,\n    RateLimitReply,\n    RateLimitInput,\n} from \"@allohouston/ddp-rate-limiter-mixin\";\n```\n\n## Migration depuis v1\n\nv2 est un **breaking change** :\n\n| Changement | v1 | v2 |\n|------------|----|----|\n| Langage | JavaScript (Babel 6) | TypeScript (strict) |\n| Format module | CJS uniquement | ESM + CJS (dual) |\n| Deps runtime | `babel-runtime` | **0** |\n| Mutabilite | Mute `methodOptions` | Retourne un **nouvel objet** |\n| `rateLimitRuleId` | Non expose | Expose dans les options retournees |\n| `errorMessage` | Non supporte | Supporte (string ou function) |\n| Validation | Verification de type basique | Stricte (NaN, Infinity, null, arrays) |\n| Node.js | Tout | **>= 20** |\n| Meteor | 1.x - 2.x | **3.4+** |\n\n## Liens\n\n- [Documentation DDPRateLimiter Meteor](https://docs.meteor.com/api/DDPRateLimiter)\n- [mdg:validated-method](https://github.com/meteor/validated-method)\n- [GitHub Packages](https://github.com/allohouston/ddp-rate-limiter-mixin/pkgs/npm/ddp-rate-limiter-mixin)\n\n</details>\n\n---\n\n## License\n\n[MIT](LICENSE)\n","readmeFilename":"README.md"}