{"_id":"@astralibx/rule-engine","_rev":"3-f1625851cdcc30fe0f1324445a6e0f59","name":"@astralibx/rule-engine","dist-tags":{"latest":"1.0.2"},"versions":{"1.0.0":{"name":"@astralibx/rule-engine","version":"1.0.0","keywords":["rule-engine","automation","handlebars","throttle","conditions"],"license":"MIT","_id":"@astralibx/rule-engine@1.0.0","maintainers":[{"name":"astralib","email":"1997hariprakash@gmail.com"}],"homepage":"https://github.com/Hariprakash1997/astralib#readme","bugs":{"url":"https://github.com/Hariprakash1997/astralib/issues"},"dist":{"shasum":"724cde05748fe7cb7c65f4e2335d0633970c4c87","tarball":"https://registry.npmjs.org/@astralibx/rule-engine/-/rule-engine-1.0.0.tgz","fileCount":8,"integrity":"sha512-vifJnMbxzXTJzuG4B1sGYYtSM1+j90lEMJawittVGxFQXdsr98ZmWbyHEH0ocz/damtqO86juz/9VJMsTw5DNA==","signatures":[{"sig":"MEQCIFs30mgeisuA6OEB0WhR05/Sh8Y0ctYo7VTB7dOQKMDnAiBNXkjQLZGP9sdSX8gSCgXZUnKamDpCCL7d8q98z2XVMw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":730911},"main":"dist/index.cjs","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.cjs"}}},"gitHead":"ae1365ff1004be6b7ef07ac5ca5a5d6d8d9a6fa6","scripts":{"dev":"tsup --watch","lint":"eslint src/","test":"vitest run","build":"tsup","clean":"rm -rf dist","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"astralib","email":"1997hariprakash@gmail.com"},"repository":{"url":"git+https://github.com/Hariprakash1997/astralib.git","type":"git","directory":"packages/rule-engine/core"},"_npmVersion":"10.9.4","description":"Platform-agnostic rule engine with templates, conditions, collection schemas, and join support","directories":{},"_nodeVersion":"22.22.1","dependencies":{"zod":"^3.23.0","handlebars":"^4.7.0","@astralibx/core":"*"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","express":"^5.0.0","ioredis":"^5.4.2","mongoose":"^8.12.1","typescript":"^5.8.2","@types/node":"^22.0.0","@types/express":"^5.0.0","@vitest/coverage-v8":"^3.0.0","mongodb-memory-server":"^10.0.0"},"peerDependencies":{"express":"^4.18.0 || ^5.0.0","ioredis":"^5.0.0","mongoose":"^7.0.0 || ^8.0.0","@astralibx/core":"^1.2.0"},"_npmOperationalInternal":{"tmp":"tmp/rule-engine_1.0.0_1773917002212_0.6954334142141096","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@astralibx/rule-engine","version":"1.0.1","keywords":["rule-engine","automation","handlebars","throttle","conditions"],"license":"MIT","_id":"@astralibx/rule-engine@1.0.1","maintainers":[{"name":"astralib","email":"1997hariprakash@gmail.com"}],"homepage":"https://github.com/Hariprakash1997/astralib#readme","bugs":{"url":"https://github.com/Hariprakash1997/astralib/issues"},"dist":{"shasum":"145e8d3c227d0671706adda8169aaf4c8597a43f","tarball":"https://registry.npmjs.org/@astralibx/rule-engine/-/rule-engine-1.0.1.tgz","fileCount":8,"integrity":"sha512-Je3TJFYYenzeNWmOIRfraAGMro76dvnyB64i5vrKGHEmHHEGsW0Ga4rMXVilEMiscznm+aoO2l4/o+I7zOmTGQ==","signatures":[{"sig":"MEQCIG4kzhp/pFR56pJci7CEXSxYwhx6mjubGw0+I1tMXS/WAiBEUvYkBubA6l/z/BSwuxd7e0W/j6PzQ/hkw7CgsKhEMg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":734569},"main":"dist/index.cjs","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.cjs"}}},"gitHead":"4d2a55b3968e55f083d4a957eb2319acb1fa5ba3","scripts":{"dev":"tsup --watch","lint":"eslint src/","test":"vitest run","build":"tsup","clean":"rm -rf dist","test:watch":"vitest","test:coverage":"vitest run --coverage"},"_npmUser":{"name":"astralib","email":"1997hariprakash@gmail.com"},"repository":{"url":"git+https://github.com/Hariprakash1997/astralib.git","type":"git","directory":"packages/rule-engine/core"},"_npmVersion":"10.9.4","description":"Platform-agnostic rule engine with templates, conditions, collection schemas, and join support","directories":{},"_nodeVersion":"22.22.1","dependencies":{"zod":"^3.23.0","handlebars":"^4.7.0","@astralibx/core":"*"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.0.0","express":"^5.0.0","ioredis":"^5.4.2","mongoose":"^8.12.1","typescript":"^5.8.2","@types/node":"^22.0.0","@types/express":"^5.0.0","@vitest/coverage-v8":"^3.0.0","mongodb-memory-server":"^10.0.0"},"peerDependencies":{"express":"^4.18.0 || ^5.0.0","ioredis":"^5.0.0","mongoose":"^7.0.0 || ^8.0.0","@astralibx/core":"^1.2.0"},"_npmOperationalInternal":{"tmp":"tmp/rule-engine_1.0.1_1773998151358_0.45682682227399063","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@astralibx/rule-engine","version":"1.0.2","description":"Platform-agnostic rule engine with templates, conditions, collection schemas, and join support","repository":{"type":"git","url":"git+https://github.com/Hariprakash1997/astralib.git","directory":"packages/rule-engine/core"},"main":"dist/index.cjs","module":"dist/index.mjs","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"require":{"types":"./dist/index.d.ts","default":"./dist/index.cjs"}}},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","lint":"eslint src/","clean":"rm -rf dist"},"keywords":["rule-engine","automation","handlebars","throttle","conditions"],"license":"MIT","dependencies":{"@astralibx/core":"*","handlebars":"^4.7.0","zod":"^3.23.0"},"peerDependencies":{"@astralibx/core":"^1.2.0","express":"^4.18.0 || ^5.0.0","ioredis":"^5.0.0","mongoose":"^7.0.0 || ^8.0.0"},"devDependencies":{"@types/express":"^5.0.0","@types/node":"^22.0.0","@vitest/coverage-v8":"^3.0.0","express":"^5.0.0","ioredis":"^5.4.2","mongoose":"^8.12.1","typescript":"^5.8.2","vitest":"^3.0.0","mongodb-memory-server":"^10.0.0"},"_id":"@astralibx/rule-engine@1.0.2","gitHead":"a262056ce04db2d3bf6f81446845b04bacabf087","bugs":{"url":"https://github.com/Hariprakash1997/astralib/issues"},"homepage":"https://github.com/Hariprakash1997/astralib#readme","_nodeVersion":"22.22.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-mmfal/SxLrg/8ccUjIJsPFfsLsNBID+cjwzutJiESZt4syBNPhVEIj4oR1UkQsXz983WY5d5A28ipCzSSZ0zUA==","shasum":"aa13165c3ba845872004a178c360386c3e603421","tarball":"https://registry.npmjs.org/@astralibx/rule-engine/-/rule-engine-1.0.2.tgz","fileCount":8,"unpackedSize":734677,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDHW8IFAP+8dgoSULPBuZbqW9JxHATkvRi/Ur8AAnvvLgIgQ9Vz8ELywc+F/lLUN7I7SCuTgiE31Kzdt/Jg8EIs0Sg="}]},"_npmUser":{"name":"astralib","email":"1997hariprakash@gmail.com"},"directories":{},"maintainers":[{"name":"astralib","email":"1997hariprakash@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rule-engine_1.0.2_1774004044308_0.615404577446891"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-19T10:43:22.157Z","modified":"2026-03-20T10:54:04.591Z","1.0.0":"2026-03-19T10:43:22.375Z","1.0.1":"2026-03-20T09:15:51.549Z","1.0.2":"2026-03-20T10:54:04.490Z"},"bugs":{"url":"https://github.com/Hariprakash1997/astralib/issues"},"license":"MIT","homepage":"https://github.com/Hariprakash1997/astralib#readme","keywords":["rule-engine","automation","handlebars","throttle","conditions"],"repository":{"type":"git","url":"git+https://github.com/Hariprakash1997/astralib.git","directory":"packages/rule-engine/core"},"description":"Platform-agnostic rule engine with templates, conditions, collection schemas, and join support","maintainers":[{"name":"astralib","email":"1997hariprakash@gmail.com"}],"readme":"# @astralibx/rule-engine\n\nPlatform-agnostic rule engine for targeting, template rendering, throttling, and send orchestration across any channel.\n\n## Features\n\n- Factory function returns `routes`, `services`, and `models` in a single call\n- Adapter-based dependency injection — your app controls user queries, data resolution, and delivery\n- Handlebars templates with multi-variant subjects and bodies (A/B rotation)\n- Templates own their data source via `collectionName` and `joins`\n- Per-user throttling with daily/weekly caps, cooldown, and deduplication via Redis\n- Distributed run locking to prevent overlapping scheduler executions\n- `query` targeting (conditions on user fields) and `list` targeting (explicit identifiers)\n- Cron scheduling per rule with timezone support\n- Full lifecycle hooks: `onRunStart`, `onRuleStart`, `onSend`, `beforeSend`, `onRunComplete`\n- Platform wrappers (`@astralibx/email-rule-engine`, `@astralibx/telegram-rule-engine`) add rendering on top\n\n## Architecture\n\n```\nConsumer App\n     │\n     ▼\ncreateRuleEngine(config)\n     │\n     ├─── routes    →  Express Router (mount anywhere)\n     ├─── services  →  { template, rule, runner }\n     └─── models    →  { Template, Rule, SendLog, RunLog, ErrorLog, ThrottleConfig }\n\nconfig.adapters\n     ├─── queryUsers(target, limit, ctx)     →  fetch matching users from your DB\n     ├─── resolveData(user)                  →  map user to Handlebars context\n     ├─── send(params)                       →  deliver the rendered message\n     ├─── selectAgent(identifierId, ctx)     →  pick a sending account\n     └─── findIdentifier(contactValue)       →  resolve contact to RecipientIdentifier\n\nconfig.collections   →  CollectionSchema[] describing joins available to templates\nconfig.platforms     →  string[] used for enum validation on templates and rules\nconfig.options       →  sendWindow, throttle defaults, jitter, delay between sends\nconfig.hooks         →  lifecycle callbacks (no library internals modified)\n```\n\n## Design Principles\n\n- **Factory pattern** — `createRuleEngine(config)` returns everything; no global singletons or static state.\n- **Adapter-based DI** — five adapter functions decouple the engine from your user model, data layer, and transport.\n- **Templates own their data source** — each template declares `collectionName` and `joins`; the engine resolves data automatically at run time.\n- **Shared collections with platform field** — one MongoDB connection serves all platforms; `platform` on every document namespaces the data.\n- **Zero business logic** — the engine handles infrastructure (throttling, locking, scheduling, logging); adapters define what gets sent to whom.\n- **Production-safe defaults** — send windows, jitter, per-run caps, and Redis locking are on by default to prevent runaway sends.\n\n## Quick Start\n\n```typescript\nimport express from 'express';\nimport mongoose from 'mongoose';\nimport Redis from 'ioredis';\nimport { createRuleEngine } from '@astralibx/rule-engine';\n\nconst app = express();\napp.use(express.json());\n\nconst db = mongoose.createConnection('mongodb://localhost:27017/myapp');\nconst redis = new Redis();\n\nconst engine = createRuleEngine({\n  db: { connection: db, collectionPrefix: 'myapp_' },\n  redis: { connection: redis, keyPrefix: 'myapp:re:' },\n\n  platforms: ['email', 'telegram'],\n  audiences: ['users', 'admins'],\n  categories: ['onboarding', 'marketing'],\n\n  adapters: {\n    queryUsers: async (target, limit, ctx) => {\n      // Use target.mode === 'query' for condition-based or 'list' for explicit ids\n      return db.collection('users').find({}).limit(limit).toArray();\n    },\n    resolveData: (user) => ({\n      user: { name: user.name, email: user.email },\n      platform: { name: 'MyApp' },\n    }),\n    send: async (params) => {\n      // params: { identifierId, contactId, accountId, subject, body, ruleId, autoApprove }\n      await myTransport.send(params);\n    },\n    selectAgent: async (identifierId, ctx) => {\n      const account = await myAccountPool.getBest();\n      if (!account) return null;\n      return { accountId: account.id, contactValue: account.address, metadata: {} };\n    },\n    findIdentifier: async (contactValue) => {\n      const rec = await db.collection('identifiers').findOne({ value: contactValue });\n      return rec ? { id: rec._id.toString(), contactId: rec.contactId } : null;\n    },\n  },\n\n  collections: [\n    {\n      name: 'orders',\n      collectionName: 'myapp_orders',\n      label: 'Orders',\n      fields: [{ name: 'status', type: 'string' }, { name: 'total', type: 'number' }],\n    },\n  ],\n\n  hooks: {\n    onSend: (info) => console.log(`Sent to ${info.contactValue} — rule: ${info.ruleName}`),\n  },\n});\n\napp.use('/api/rule-engine', engine.routes);\napp.listen(3000);\n```\n\n## API Routes\n\nAll routes are mounted under the prefix you choose (e.g. `/api/rule-engine`).\n\n| Resource | Routes |\n|----------|--------|\n| **Templates** | `GET /templates` · `POST /templates` · `POST /templates/validate` · `POST /templates/preview` · `GET /:id` · `PUT /:id` · `DELETE /:id` · `PATCH /:id/toggle` · `POST /:id/preview` · `POST /:id/preview-with-data` · `POST /:id/test-send` · `POST /:id/clone` |\n| **Rules** | `GET /rules` · `POST /rules` · `POST /rules/preview-conditions` · `GET /:id` · `PATCH /:id` · `DELETE /:id` · `POST /:id/toggle` · `POST /:id/dry-run` · `POST /:id/clone` |\n| **Runner** | `POST /runner` · `GET /runner/status` · `GET /runner/status/:runId` · `POST /runner/cancel/:runId` · `GET /runner/logs` |\n| **Sends** | `GET /sends` |\n| **Collections** | `GET /collections` · `GET /collections/:name/fields` |\n| **Settings** | `GET /throttle` · `PUT /throttle` |\n\n## Platform Wrappers\n\n`@astralibx/email-rule-engine` and `@astralibx/telegram-rule-engine` are thin wrappers around this package. They pre-wire platform-specific rendering (MJML + Handlebars for email, Markdown for Telegram) and re-export `createRuleEngine` as `createEmailRuleEngine` / `createTelegramRuleEngine`. Use this core package directly when building a custom channel.\n\n## Getting Started\n\n- [Quick Start Tutorial](https://github.com/Hariprakash1997/astralib/blob/main/packages/rule-engine/core/docs/quick-start-tutorial.md) — end-to-end walkthrough from install to first send\n- [Adapters](https://github.com/Hariprakash1997/astralib/blob/main/packages/rule-engine/core/docs/adapters.md) — contract for each of the five adapter functions\n- [Collections and Joins](https://github.com/Hariprakash1997/astralib/blob/main/packages/rule-engine/core/docs/collections-and-joins.md) — expose MongoDB collections as data sources for templates\n- [Templates and Rules](https://github.com/Hariprakash1997/astralib/blob/main/packages/rule-engine/core/docs/templates-and-rules.md) — authoring templates, conditions, scheduling, and throttle overrides\n- [Throttling and Hooks](https://github.com/Hariprakash1997/astralib/blob/main/packages/rule-engine/core/docs/throttling-and-hooks.md) — per-user caps, send windows, jitter, and lifecycle hooks\n- [Glossary](https://github.com/Hariprakash1997/astralib/blob/main/packages/rule-engine/core/docs/glossary.md) — terms, constants, ID types, and common gotchas\n- [Changelog](https://github.com/Hariprakash1997/astralib/blob/main/packages/rule-engine/core/CHANGELOG.md)\n\n## License\n\nMIT\n","readmeFilename":"README.md"}