{"id":30525617,"url":"https://github.com/silent-watcher/mongoose-reactions","last_synced_at":"2025-08-31T04:03:26.226Z","repository":{"id":311399229,"uuid":"1043540039","full_name":"Silent-Watcher/mongoose-reactions","owner":"Silent-Watcher","description":null,"archived":false,"fork":false,"pushed_at":"2025-08-24T09:07:30.000Z","size":64,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-08-24T14:07:13.840Z","etag":null,"topics":["mongodb","mongoose","mongoose-js","mongoose-plugin","node-mongoose"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/mongoose-reactions","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Silent-Watcher.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-24T04:39:10.000Z","updated_at":"2025-08-24T10:04:30.000Z","dependencies_parsed_at":"2025-08-24T14:07:19.527Z","dependency_job_id":"6bdd93d2-0ccf-4933-892c-30524ad21f7d","html_url":"https://github.com/Silent-Watcher/mongoose-reactions","commit_stats":null,"previous_names":["silent-watcher/mongoose-reactions"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/Silent-Watcher/mongoose-reactions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Silent-Watcher%2Fmongoose-reactions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Silent-Watcher%2Fmongoose-reactions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Silent-Watcher%2Fmongoose-reactions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Silent-Watcher%2Fmongoose-reactions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Silent-Watcher","download_url":"https://codeload.github.com/Silent-Watcher/mongoose-reactions/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Silent-Watcher%2Fmongoose-reactions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272793017,"owners_count":24993831,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mongodb","mongoose","mongoose-js","mongoose-plugin","node-mongoose"],"created_at":"2025-08-26T23:00:53.351Z","updated_at":"2025-08-30T02:00:32.809Z","avatar_url":"https://github.com/Silent-Watcher.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\u003cdiv align=\"center\"\u003e\n\n  \u003ch1\u003emongoose-reactions\u003c/h1\u003e\n\n  \u003cp\u003e\n\t\u003ca href=\"#features\"\u003efeatures\u003c/a\u003e •\n\t\u003ca href=\"#Installation\"\u003eInstallation\u003c/a\u003e •\n\t\u003ca href=\"#quick-start\"\u003eQuick Start\u003c/a\u003e\n  \u003c/p\u003e\n\n\n  \u003cp\u003e\n    \u003ca href=\"https://github.com/Silent-Watcher/mongoose-reactions/blob/master/LICENSE\"\u003e\n      \u003cimg src=\"https://img.shields.io/github/license/Silent-Watcher/mongoose-reactions?color=#2fb64e\"license\"\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\n\u003c/div\u003e\n\n\n\n**Mongoose‑Reactions** is a TypeScript‑first Mongoose plugin that adds polymorphic reaction support (like 👍, ❤️, 😂, or any custom emoji) to any Mongoose model.\n\n\u003e It works with both single‑reaction‑per‑user and multi‑reaction‑per‑user modes, offers a full‑featured static and instance API, and is fully typed.\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Configuration Options](#configuration-options)\n- [Static API](#static-api)\n- [Instance API](#instance-api)\n- [Advanced Usage](#advanced-usage)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Features\n\n| ✅ | Feature |\n|---|---------|\n| ✅ | **Polymorphic reactions** – attach reactions to any model (posts, comments, users, etc.). |\n| ✅ | **Single‑ or multi‑reaction per user** – choose whether a user can have only one reaction per reactable or many different reactions. |\n| ✅ | **Whitelist \u0026 case‑insensitivity** – restrict allowed reaction types and optionally treat them case‑insensitively. |\n| ✅ | **Typed API** – full TypeScript definitions for all static and instance methods. |\n| ✅ | **Atomic upserts** – uses MongoDB unique indexes to guarantee consistency under concurrency. |\n| ✅ | **Aggregation helpers** – quickly get reaction counts per type. |\n| ✅ | **Session support** – all operations accept an optional `ClientSession` for transaction safety. |\n| ✅ | **Custom metadata** – store arbitrary extra data (`meta`) with each reaction. |\n\n---\n\n## Installation\n\n```bash\n# Using npm\nnpm install mongoose-reactions\n\n# Using yarn\nyarn add mongoose-reactions\n\n# Using pnpm\npnpm add mongoose-reactions\n```\n\n\u003e **Peer dependency:** `mongoose@^8.18.0`. Make sure Mongoose is installed in your project.\n\n---\n\n## Quick Start\n\n```ts\nimport mongoose from 'mongoose';\nimport { reactionsPlugin } from 'mongoose-reactions';\n\n// Define a simple Post schema\nconst postSchema = new mongoose.Schema({\n  title: String,\n  content: String,\n});\n\n// Apply the plugin (default options)\npostSchema.plugin(reactionsPlugin);\n\nconst Post = mongoose.model('Post', postSchema);\n\n// --- Using the static API ---\nasync function demo() {\n  const post = await Post.create({ title: 'Hello', content: 'World' });\n\n  // User 1 likes the post\n  await Post.react(post._id, '60c72b2f9f1b2c001c8d4e9a', 'like');\n\n  // User 2 loves the post with extra meta\n  await Post.react(post._id, '60c72b2f9f1b2c001c8d4e9b', 'love', { source: 'mobile' });\n\n  // Toggle a reaction (remove if exists, add otherwise)\n  await Post.toggleReaction(post._id, '60c72b2f9f1b2c001c8d4e9a', 'like');\n\n  // Get counts\n  const counts = await Post.getReactionCounts(post._id);\n  console.log(counts); // { like: 0, love: 1 }\n\n  // List all reactors for a specific reaction\n  const lovers = await Post.listReactors(post._id, { reaction: 'love' });\n  console.log(lovers);\n}\n```\n\n---\n\n## Configuration Options\n\nWhen applying the plugin you can pass a `PluginOptions` object:\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `allowMultipleReactionsPerUser` | `boolean` | `false` | If `true`, a user may store many different reactions on the same reactable. |\n| `reactionTypes` | `string[]` | `undefined` | Whitelist of allowed reaction identifiers (e.g. `['like', 'love', 'haha']`). |\n| `reactionTypesCaseInsensitive` | `boolean` | `true` | Convert incoming reaction strings to lower‑case before validation. |\n| `reactionModelName` | `string` | `'Reaction'` | Name of the internal Mongoose model that stores reactions. |\n\n```ts\n// Example: enable multiple reactions and whitelist\npostSchema.plugin(reactionsPlugin, {\n  allowMultipleReactionsPerUser: true,\n  reactionTypes: ['like', 'love', 'haha', 'wow'],\n  reactionTypesCaseInsensitive: false,\n});\n```\n\n---\n\n## Static API\n\n| Method | Signature | Description |\n|--------|-----------|-------------|\n| `react` | `react(reactableId, userId, reaction, meta?, opOpts?)` | Add a reaction (or update it in single‑mode). Returns the created/updated document. |\n| `unreact` | `unreact(reactableId, userId, reaction?, opOpts?)` | Remove a specific reaction (if provided) or all reactions of a user on the reactable. Returns number of deleted docs. |\n| `toggleReaction` | `toggleReaction(reactableId, userId, reaction, meta?, opOpts?)` | Flip the presence of a reaction. Returns `{removed:true}` or the created/updated document. |\n| `getReactionCounts` | `getReactionCounts(reactableId, opOpts?)` | Returns an object mapping reaction types → counts. |\n| `getUserReactions` | `getUserReactions(reactableId, userId, options?)` | Fetch reactions a user has made on a reactable (array of docs). |\n| `listReactors` | `listReactors(reactableId, opts?)` | Paginated list of all reaction documents for a reactable, optionally filtered by reaction type. |\n\nAll static methods accept an optional `opOpts` object with a `session` field to run inside a MongoDB transaction.\n\n---\n\n## Instance API\n\nWhen the plugin is applied, each document gains the following methods:\n\n| Method | Signature | Description |\n|--------|-----------|-------------|\n| `react` | `(userId, reaction, meta?)` | Shortcut to the static `react` using the document’s `_id`. |\n| `unreact` | `(userId, reaction?)` | Shortcut to the static `unreact` for this document. |\n\nExample:\n\n```ts\nconst post = await Post.findById(id);\nawait post.react('60c72b2f9f1b2c001c8d4e9a', 'like');\nawait post.unreact('60c72b2f9f1b2c001c8d4e9a');\n```\n\n---\n\n## Advanced Usage\n\n### Transactions\n\n```ts\nconst session = await mongoose.startSession();\nsession.startTransaction();\n\ntry {\n  await Post.react(postId, userId, 'like', undefined, { session });\n  await SomeOtherModel.updateOne(..., { session });\n  await session.commitTransaction();\n} catch (e) {\n  await session.abortTransaction();\n  throw e;\n} finally {\n  session.endSession();\n}\n```\n\n### Custom Reaction Model Name\n\nIf you need a differently named collection:\n\n```ts\npostSchema.plugin(reactionsPlugin, { reactionModelName: 'PostReaction' });\n```\n\nThe collection will be `postreactions` (Mongoose pluralizes automatically).\n\n### Adding Extra Fields\n\nYou can extend the internal reaction schema via Mongoose discriminators or by creating a separate model that references the generated one. The plugin stores only the core fields (`reactableId`, `reactableModel`, `user`, `reaction`, `meta`, timestamps).\n\n---\n\n## Contributing\n\n1. **Fork** the repository.\n2. **Create a branch** for your feature or bugfix.\n3. **Write tests** for any new functionality.\n4. **Run lint \u0026 format**: `npm run lint` and `npm run prelint`.\n5. **Commit** using Conventional Commits (`npm run commit`).\n6. **Open a Pull Request** – the CI will run linting, tests, and coverage checks automatically.\n\nPlease read the full [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on coding style, commit messages, and issue reporting.\n\n---\n\n## License\n\nMIT © 2025 Ali Nazari\n\nSee the full license text in the [LICENSE](./LICENSE) file.\n\n---\n\n## Contact\n\nIf you encounter bugs or have feature ideas, feel free to open an issue or contact the maintainer at **backendwithali@gmail.com**.\n\n---\n\n*Happy reacting!*\n\n---\n\n\u003cdiv align=\"center\"\u003e\n  \u003cp\u003e\n    \u003csub\u003eBuilt with ❤️ by \u003ca href=\"https://github.com/Silent-Watcher\" target=\"_blank\"\u003eAli Nazari\u003c/a\u003e, for developers.\u003c/sub\u003e\n  \u003c/p\u003e\n  \u003cp\u003e\n    \u003ca href=\"https://github.com/Silent-Watcher/mongoose-reactions\"\u003e⭐ Star us on GitHub\u003c/a\u003e •\n    \u003ca href=\"https://www.linkedin.com/in/alitte/\"\u003e🐦 Follow on Linkedin\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsilent-watcher%2Fmongoose-reactions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsilent-watcher%2Fmongoose-reactions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsilent-watcher%2Fmongoose-reactions/lists"}