{"_id":"@ahrowe/mongo","_rev":"6-b7065b5364aad1b5391514df3910466b","name":"@ahrowe/mongo","dist-tags":{"latest":"0.1.2"},"versions":{"0.0.1":{"name":"@ahrowe/mongo","version":"0.0.1","keywords":["mongodb","mongo","crud","transactions"],"author":{"name":"AndreasWeinzierl"},"license":"MIT","_id":"@ahrowe/mongo@0.0.1","maintainers":[{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"}],"homepage":"https://github.com/AndreasWeinzierl/ahrowe-mongo#readme","bugs":{"url":"https://github.com/AndreasWeinzierl/ahrowe-mongo/issues"},"dist":{"shasum":"d72d027c02332df3ad3700e07fb5679da9198b75","tarball":"https://registry.npmjs.org/@ahrowe/mongo/-/mongo-0.0.1.tgz","fileCount":23,"integrity":"sha512-ds6VtH/n3uDbQNPJqEDSQQddNad0YFu7r8Cy9fA8yAcQgz7iDdSiRnWEJ8plU97ayjZDV1McAyzL/GHNv3jI/g==","signatures":[{"sig":"MEUCIQDdByf+yWXDESuyT+oc4QkQGi43vFa81gEJDC+r3nHbuAIgWgV7ISEfXbQO5RJERmeY04Q698FlkGOa8Kc9tMdc5bU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":121393},"main":"./dist/index.js","type":"module","_from":"file:ahrowe-mongo-0.0.1.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"lint":"eslint .","test":"vitest","build":"tsc","release":"bash scripts/release.sh","prebuild":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""},"_npmUser":{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"},"_resolved":"/tmp/348ba7cbe1ef5c9b9526bf7ffb7af6cb/ahrowe-mongo-0.0.1.tgz","_integrity":"sha512-ds6VtH/n3uDbQNPJqEDSQQddNad0YFu7r8Cy9fA8yAcQgz7iDdSiRnWEJ8plU97ayjZDV1McAyzL/GHNv3jI/g==","repository":{"url":"git+https://github.com/AndreasWeinzierl/ahrowe-mongo.git","type":"git"},"_npmVersion":"11.9.0","description":"Typed MongoDB CRUD service factory with built-in transactions, event hooks, and optional document validation.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"lodash":"^4.17.21","mongodb":"^7.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^10.5.0","vitest":"^4.1.8","globals":"^17.6.0","@eslint/js":"^10.0.1","typescript":"^6.0.3","@types/node":"^24.13.2","@types/lodash":"^4.17.21","testcontainers":"^11.9.0","@faker-js/faker":"^10.1.0","typescript-eslint":"^8.61.0"},"_npmOperationalInternal":{"tmp":"tmp/mongo_0.0.1_1783775284703_0.008688569228866072","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@ahrowe/mongo","version":"0.0.2","keywords":["mongodb","mongo","crud","transactions"],"author":{"name":"AndreasWeinzierl"},"license":"MIT","_id":"@ahrowe/mongo@0.0.2","maintainers":[{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"}],"homepage":"https://github.com/AndreasWeinzierl/ahrowe-mongo#readme","bugs":{"url":"https://github.com/AndreasWeinzierl/ahrowe-mongo/issues"},"dist":{"shasum":"4c84fca30e94021595a5324084f53fb528e862d2","tarball":"https://registry.npmjs.org/@ahrowe/mongo/-/mongo-0.0.2.tgz","fileCount":23,"integrity":"sha512-ZDIR778Tn/oZB1CvTcQERaHYy5gudXJq7kJm5lEdRT8sNM6I6skHP8t3GVMFT3376eFbxJOXXmMTfkkuWua6Ow==","signatures":[{"sig":"MEYCIQCuCYB3RBfWzipfHmuGiV49heQiS9XmgMwI+KtGRDkeDgIhAN4fEqAzKjbkvbALPeC8bN57bdjdZrbGVUJHb74tb4RK","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":128047},"main":"./dist/index.js","type":"module","_from":"file:ahrowe-mongo-0.0.2.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"lint":"eslint .","test":"vitest","build":"tsc","release":"bash scripts/release.sh","prebuild":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""},"_npmUser":{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"},"_resolved":"/tmp/aeeace21be9f0054db90e14655d0d0c5/ahrowe-mongo-0.0.2.tgz","_integrity":"sha512-ZDIR778Tn/oZB1CvTcQERaHYy5gudXJq7kJm5lEdRT8sNM6I6skHP8t3GVMFT3376eFbxJOXXmMTfkkuWua6Ow==","repository":{"url":"git+https://github.com/AndreasWeinzierl/ahrowe-mongo.git","type":"git"},"_npmVersion":"11.9.0","description":"Typed MongoDB CRUD service factory with built-in transactions, event hooks, and optional document validation.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"lodash":"^4.17.21","mongodb":"^7.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^10.5.0","vitest":"^4.1.8","globals":"^17.6.0","@eslint/js":"^10.0.1","typescript":"^6.0.3","@types/node":"^24.13.2","@types/lodash":"^4.17.21","testcontainers":"^11.9.0","@faker-js/faker":"^10.1.0","typescript-eslint":"^8.61.0"},"_npmOperationalInternal":{"tmp":"tmp/mongo_0.0.2_1785953276137_0.4558714706847029","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@ahrowe/mongo","version":"0.0.3","keywords":["mongodb","mongo","crud","transactions"],"author":{"name":"AndreasWeinzierl"},"license":"MIT","_id":"@ahrowe/mongo@0.0.3","maintainers":[{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"}],"homepage":"https://github.com/AndreasWeinzierl/ahrowe-mongo#readme","bugs":{"url":"https://github.com/AndreasWeinzierl/ahrowe-mongo/issues"},"dist":{"shasum":"8f90cda769146fe0a85dcf2ba7bac8c69a0b3883","tarball":"https://registry.npmjs.org/@ahrowe/mongo/-/mongo-0.0.3.tgz","fileCount":23,"integrity":"sha512-l/yK49hlbejO4xEc+0Rd1PwibIp/42KCLMouPZQbd5W8LWxAcsJjPHthBB4ZjNMoYkBPbMSNP9+INlU2m7dpGg==","signatures":[{"sig":"MEYCIQDSB3mvBBnoF46bjEVoQBfANto0DVAimdAJey+kwdp3qQIhAM+drrxgSCkxbmy2AOj7OznxHpzRX7QuJeL6w97b4zsW","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":131623},"main":"./dist/index.js","type":"module","_from":"file:ahrowe-mongo-0.0.3.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"lint":"eslint .","test":"vitest","build":"tsc","release":"bash scripts/release.sh","prebuild":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""},"_npmUser":{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"},"_resolved":"/tmp/9f19f3b469e47094fe981acc81da6c2b/ahrowe-mongo-0.0.3.tgz","_integrity":"sha512-l/yK49hlbejO4xEc+0Rd1PwibIp/42KCLMouPZQbd5W8LWxAcsJjPHthBB4ZjNMoYkBPbMSNP9+INlU2m7dpGg==","repository":{"url":"git+https://github.com/AndreasWeinzierl/ahrowe-mongo.git","type":"git"},"_npmVersion":"11.9.0","description":"Typed MongoDB CRUD service factory with built-in transactions, event hooks, and optional document validation.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"lodash":"^4.17.21","mongodb":"^7.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^10.5.0","vitest":"^4.1.8","globals":"^17.6.0","@eslint/js":"^10.0.1","typescript":"^6.0.3","@types/node":"^24.13.2","@types/lodash":"^4.17.21","testcontainers":"^11.9.0","@faker-js/faker":"^10.1.0","typescript-eslint":"^8.61.0"},"_npmOperationalInternal":{"tmp":"tmp/mongo_0.0.3_1787594279878_0.4174289949377108","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@ahrowe/mongo","version":"0.1.0","keywords":["mongodb","mongo","crud","transactions"],"author":{"name":"AndreasWeinzierl"},"license":"MIT","_id":"@ahrowe/mongo@0.1.0","maintainers":[{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"}],"homepage":"https://github.com/AndreasWeinzierl/ahrowe-mongo#readme","bugs":{"url":"https://github.com/AndreasWeinzierl/ahrowe-mongo/issues"},"dist":{"shasum":"f72d2366cffa217893017035113f7f8b74578f9f","tarball":"https://registry.npmjs.org/@ahrowe/mongo/-/mongo-0.1.0.tgz","fileCount":23,"integrity":"sha512-aYRhjGjpxC6ddkfceaz6PJaXqh8eEaLeOVtgjSh6BLuBCrXgY89REEexB1n8rWqffXzN8uj5jPA3D9Z0b+rL/Q==","signatures":[{"sig":"MEYCIQD2Gk9Oi18oxHSCuLsI1W5cpTyZ+1B4nU6EpiktPorMqAIhAK39DZUCog2O7K7fLPgmk7HDeA8MEQgES4yUv2arbcRj","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":132508},"main":"./dist/index.js","type":"module","_from":"file:ahrowe-mongo-0.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"lint":"eslint .","test":"vitest","build":"tsc","release":"bash scripts/release.sh","prebuild":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""},"_npmUser":{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"},"_resolved":"/tmp/41df5bfcd31f957d9f0c9f5149588617/ahrowe-mongo-0.1.0.tgz","_integrity":"sha512-aYRhjGjpxC6ddkfceaz6PJaXqh8eEaLeOVtgjSh6BLuBCrXgY89REEexB1n8rWqffXzN8uj5jPA3D9Z0b+rL/Q==","repository":{"url":"git+https://github.com/AndreasWeinzierl/ahrowe-mongo.git","type":"git"},"_npmVersion":"11.9.0","description":"Typed MongoDB CRUD service factory with built-in transactions, event hooks, and optional document validation.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"lodash":"^4.17.21","mongodb":"^7.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^10.5.0","vitest":"^4.1.8","globals":"^17.6.0","@eslint/js":"^10.0.1","typescript":"^6.0.3","@types/node":"^24.13.2","@types/lodash":"^4.17.21","testcontainers":"^11.9.0","@faker-js/faker":"^10.1.0","typescript-eslint":"^8.61.0"},"_npmOperationalInternal":{"tmp":"tmp/mongo_0.1.0_1787851179239_0.9406371457113711","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@ahrowe/mongo","version":"0.1.1","keywords":["mongodb","mongo","crud","transactions"],"author":{"name":"AndreasWeinzierl"},"license":"MIT","_id":"@ahrowe/mongo@0.1.1","maintainers":[{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"}],"homepage":"https://github.com/AndreasWeinzierl/ahrowe-mongo#readme","bugs":{"url":"https://github.com/AndreasWeinzierl/ahrowe-mongo/issues"},"dist":{"shasum":"8dc450bc535d65391cf5a12fd2f9789de36ec33c","tarball":"https://registry.npmjs.org/@ahrowe/mongo/-/mongo-0.1.1.tgz","fileCount":23,"integrity":"sha512-8jH8oiUmqtBzXdf8vF3K3KcQby0jykzRczQqtIuWcJEEus1U27/qB8HjKr5J115XbgoVz5wa6YHb+uFdV8F1pg==","signatures":[{"sig":"MEYCIQCnQ8c4qXshr8ih9WkgZ3yXzcP2bUUgzF4Ndzz8ayE/sgIhALPf2lhHloFLXngmoHTG6GHEcmcUdApEN/p3nBx3KsD8","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":110540},"main":"./dist/index.js","type":"module","_from":"file:ahrowe-mongo-0.1.1.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"lint":"eslint .","test":"vitest","build":"tsc","release":"bash scripts/release.sh","prebuild":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""},"_npmUser":{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"},"_resolved":"/tmp/87ff129c11845106a51a7fc8a48dd5f4/ahrowe-mongo-0.1.1.tgz","_integrity":"sha512-8jH8oiUmqtBzXdf8vF3K3KcQby0jykzRczQqtIuWcJEEus1U27/qB8HjKr5J115XbgoVz5wa6YHb+uFdV8F1pg==","repository":{"url":"git+https://github.com/AndreasWeinzierl/ahrowe-mongo.git","type":"git"},"_npmVersion":"11.9.0","description":"Typed MongoDB CRUD service factory with built-in transactions, event hooks, and optional document validation.","directories":{},"sideEffects":false,"_nodeVersion":"24.14.0","dependencies":{"lodash":"^4.17.21","mongodb":"^7.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^10.5.0","vitest":"^4.1.8","globals":"^17.6.0","@eslint/js":"^10.0.1","typescript":"^6.0.3","@types/node":"^24.13.2","@types/lodash":"^4.17.21","testcontainers":"^11.9.0","@faker-js/faker":"^10.1.0","typescript-eslint":"^8.61.0"},"_npmOperationalInternal":{"tmp":"tmp/mongo_0.1.1_1788549681545_0.5984567074301339","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@ahrowe/mongo","version":"0.1.2","type":"module","license":"MIT","description":"Typed MongoDB CRUD service factory with built-in transactions, event hooks, and optional document validation.","keywords":["mongodb","mongo","crud","transactions"],"author":{"name":"AndreasWeinzierl"},"repository":{"type":"git","url":"git+https://github.com/AndreasWeinzierl/ahrowe-mongo.git"},"bugs":{"url":"https://github.com/AndreasWeinzierl/ahrowe-mongo/issues"},"homepage":"https://github.com/AndreasWeinzierl/ahrowe-mongo#readme","main":"./dist/index.js","types":"./dist/index.d.ts","sideEffects":false,"exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"}},"publishConfig":{"access":"public"},"dependencies":{"lodash":"^4.17.21","mongodb":"^7.0.0"},"devDependencies":{"@eslint/js":"^10.0.1","@faker-js/faker":"^10.1.0","@types/lodash":"^4.17.21","@types/node":"^24.13.2","eslint":"^10.5.0","globals":"^17.6.0","testcontainers":"^11.9.0","typescript":"^6.0.3","typescript-eslint":"^8.61.0","vitest":"^4.1.8"},"scripts":{"prebuild":"node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"","build":"tsc","lint":"eslint .","test":"vitest","release":"bash scripts/release.sh"},"_id":"@ahrowe/mongo@0.1.2","_integrity":"sha512-7LuCSIXaDQ9j3aTg+O5TlK7sH4t8YxsE/Syw2z954i/JRds1a6CJkjsNuF/FRg88NmYpjfKgYqhPwq9nFEzrkw==","_resolved":"/tmp/258fe4a3a52601a571d11541d3db09ba/ahrowe-mongo-0.1.2.tgz","_from":"file:ahrowe-mongo-0.1.2.tgz","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-7LuCSIXaDQ9j3aTg+O5TlK7sH4t8YxsE/Syw2z954i/JRds1a6CJkjsNuF/FRg88NmYpjfKgYqhPwq9nFEzrkw==","shasum":"da0d46614a2c0630b03b4592c318b931faeddcab","tarball":"https://registry.npmjs.org/@ahrowe/mongo/-/mongo-0.1.2.tgz","fileCount":23,"unpackedSize":111655,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCKRU+X7F1JBaDGlNrdFeXmlrgnMXt3Xkseqz6JZx3oFgIgIrzbKqfjTVkGj3XOjHksRW8+6rc8di/RkKeOysjZQn0="}]},"_npmUser":{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"},"directories":{},"maintainers":[{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mongo_0.1.2_1788551037394_0.7754957429250395"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-11T13:08:04.517Z","modified":"2026-09-04T19:43:57.712Z","0.0.1":"2026-07-11T13:08:04.827Z","0.0.2":"2026-08-05T18:07:56.299Z","0.0.3":"2026-08-24T17:58:00.040Z","0.1.0":"2026-08-27T17:19:39.384Z","0.1.1":"2026-09-04T19:21:21.736Z","0.1.2":"2026-09-04T19:43:57.536Z"},"bugs":{"url":"https://github.com/AndreasWeinzierl/ahrowe-mongo/issues"},"author":{"name":"AndreasWeinzierl"},"license":"MIT","homepage":"https://github.com/AndreasWeinzierl/ahrowe-mongo#readme","keywords":["mongodb","mongo","crud","transactions"],"repository":{"type":"git","url":"git+https://github.com/AndreasWeinzierl/ahrowe-mongo.git"},"description":"Typed MongoDB CRUD service factory with built-in transactions, event hooks, and optional document validation.","maintainers":[{"name":"plaguedoctor19","email":"ahrowe.dev@gmail.com"}],"readme":"# @ahrowe/mongo\n\nA typed MongoDB CRUD service factory: `connect()` once, then `createService<T>(...)`\ngives you a collection wrapper with built-in transactions, an event bus\n(`created`/`updated`/`removed`), and optional document validation on writes.\n\n## Install\n\n```bash\npnpm add @ahrowe/mongo\n```\n\n## Usage\n\n```ts\nimport db from '@ahrowe/mongo';\n\nconst { createService } = db.connect(process.env.MONGO_DB_URI!, 'myDb');\n\ntype User = { _id: string; email: string; createdOn?: Date; updatedOn?: Date };\nconst users = createService<User>('users');\n\nconst user = await users.insert({ email: 'a@b.com' });\nconst found = await users.findOne({ _id: user._id });\nawait users.updateOne({ _id: user._id }, { $set: { email: 'c@d.com' } });\n```\n\nCall `createService` once per collection and reuse that instance everywhere.\nCalling it again for the same collection name is supported (it reuses the\nfirst call's event bus and logs a warning, so listeners on either instance\nstill fire) but other per-instance options are **not** shared: a mismatched\n`emitOutboxEvents` means writes through one instance never fire events at all\n(not a bus issue — no outbox row is written), a mismatched `validate` means\nthe two instances enforce different schemas on the same collection, and a\nmismatched `addCreatedOnField`/`addUpdatedOnField` means only some documents\nget timestamped. Also, `eventBus.removeAllListeners()` on either instance\nclears listeners registered via both. Prefer sharing one instance unless one\nof these divergences is what you actually want (e.g. a backfill instance with\n`emitOutboxEvents: false`).\n\n### Validation on writes\n\nPass a validator function as the second argument to `createService`. It's called on\nevery insert and on the document resulting from an update; a non-passing result\nthrows. The validator contract is `(entity: T) => { error?: unknown }` — plug in\nwhatever validation library you like, or none at all. On a non-empty `error`, that\nvalue is thrown as-is.\n\nWith Zod:\n\n```ts\nimport { z } from 'zod';\n\nconst userSchema = z.object({ _id: z.string(), email: z.string().email() });\nconst users = createService<User>('users', (entity) => userSchema.safeParse(entity));\n```\n\nOr a plain TS guard with no dependency at all:\n\n```ts\nconst users = createService<User>('users', (entity) => (\n  entity.email.includes('@') ? {} : { error: new Error('email must contain @') }\n));\n```\n\nPass `{ skipValidation: true }` on an individual call to bypass it.\n\n### Events (transactional outbox)\n\n```ts\nusers.on('created', ({ doc, meta }) => { /* ... */ });\nusers.on('updated', ({ prevDoc, doc, meta }) => { /* ... */ });\nusers.on('removed', ({ doc, meta }) => { /* ... */ });\n```\n\nWrites don't dispatch events directly. Every `insert`/`updateOne`/`updateMany`/\n`removeOne`/`removeMany` that actually changes a document writes one row per\naffected document to an internal `outbox` collection, **in the same transaction**\nas the data write. A separate relay process claims and dispatches those rows to\nyour `.on(...)` listeners. This makes delivery durable: events survive a crash\nbetween the write and the listener running, and are never lost or duplicated\nacross multiple pods racing the same row (claims are leased atomically).\n`updateOne`/`updateMany` only enqueue `'updated'` for documents that actually\nchanged.\n\n`updateMany`/`removeMany` always process every matched document individually —\none atomic operation per document, all inside one transaction, so the whole\nbatch still rolls back together on failure — so each outbox row's\n`{ prevDoc, doc }` pair is accurate and no document is missed even if the\nmatched set changes mid-operation. Run the relay to drain the outbox:\n\n```ts\nimport { startOutboxRelay } from '@ahrowe/mongo';\n\nconst relay = startOutboxRelay({ instanceId: process.env.HOSTNAME ?? 'local' });\n// relay.stop() on shutdown\n```\n\nThe reliable per-document path has **no size cap by default** — but a single\ntransaction processing a very large matched set risks hitting MongoDB's\ntransaction lifetime limit. Pass `{ maxBatchSize }` (per-call, or as a\n`createService` option for a service-wide default) to enforce one and fail fast\ninstead:\n\n```ts\nawait users.updateMany({}, { $set: { plan: 'pro' } }, { maxBatchSize: 500 });\n```\n\nPast that limit it throws, pointing you at `bulkWrite` for larger jobs (no\ntransaction/validation/event guarantees, but no cap either).\n\nPass `{ meta: {...} }` on a call to thread arbitrary data through to listeners,\nso a specific handler can decide to skip its own side effect without anyone else\nmissing the event:\n\n```ts\nawait users.updateOne({ _id }, { $set: { email: 'c@d.com' } }, { meta: { dontRegenPdf: true } });\n\nusers.on('updated', ({ doc, meta }) => {\n  if (meta?.dontRegenPdf) return;\n  regenerateInvoicePdf(doc);\n});\n```\n\nInfra collections with no listeners (sequences, the outbox itself, etc.) can\nskip outbox writes entirely by passing `{ emitOutboxEvents: false }` to\n`createService` — writes take a leaner fast path with no pre-reads or\ntransaction-wrapped outbox insert.\n\nIf a listener needs request/actor context (e.g. an audit log reading from\n`AsyncLocalStorage`), register a provider once at startup. It's called\n**synchronously at write time** and the captured value is stored on the outbox\nrow, so it's still available to the listener even after a process restart:\n\n```ts\nimport { setOutboxContextProvider } from '@ahrowe/mongo';\n\nsetOutboxContextProvider(() => myAsyncLocalStorage.getStore());\n```\n\n### Transactions\n\n```ts\nimport db from '@ahrowe/mongo';\n\nconst session = await db.startSession();\nawait session.withTransaction(async () => {\n  await users.insert({ email: 'a@b.com' }, { session });\n  await otherService.updateOne({ _id }, { $set: { count: 1 } }, { session });\n});\n```\n\n`insert`/`updateOne`/`updateMany` automatically wrap themselves in a transaction when\nno `session` is passed.\n\n### Connection health\n\n```ts\nconst { createService, on } = db.connect(process.env.MONGO_DB_URI!, 'myDb');\n\non('error', ({ source, error }) => { /* 'primary' | 'read' */ });\non('close', ({ source, error }) => { /* ... */ });\n```\n\n`connect()` wires up `error`/`close` handlers on both the primary and\nsecondary-preferred read clients internally (so a connection issue can't crash the\nprocess), and re-emits them on a connection-level bus you can subscribe to for your\nown alerting.\n\n### Shutdown\n\n```ts\nimport db from '@ahrowe/mongo';\n\nawait db.disconnect();\n```\n\n`disconnect()` closes both MongoDB clients. Outbox rows already committed are\ndurable in MongoDB regardless — call this on graceful shutdown (e.g. on\n`SIGTERM`); if you also run `startOutboxRelay` in-process, call `relay.stop()`\nfirst so it releases any leases it's holding.\n\n## API\n\n- `connect(connectionString, databaseName?) → { createService, on }`\n- `createService<T>(collectionName, validate?, { addCreatedOnField?, addUpdatedOnField?, maxLimit?, maxBatchSize?, emitOutboxEvents? }) → DbService<T>`\n- `withSession(cb)`, `startSession(options?)`, `disconnect()`\n- `setOutboxContextProvider(fn)` — capture request/actor context at write time for outbox rows\n- `startOutboxRelay({ instanceId, pollIntervalMs?, batchSize?, leaseMs?, maxAttempts?, autoStart? }) → { stop, tick, drain }`\n- `getServiceBus(collectionName)`, `getRegisteredCollections()`, `getOutboxCollection()` — relay/advanced internals\n- `OutboxOp`, `OutboxStatus`, `OutboxRow`, `OUTBOX_COLLECTION` — outbox row shape, for tooling/inspection\n- `DbService<T>` methods: `find`, `findOne`, `findCursor`, `insert`, `updateOne`,\n  `updateMany`, `removeOne`, `removeMany`, `count`, `exists`, `aggregate`, `distinct`,\n  `createIndex`, `dropIndex`, `bulkWrite`, `on`, `onPropertiesUpdated`, `eventBus`, `generateId`, `name`\n\nSee [docs/CLAUDE.md](docs/CLAUDE.md) for gotchas (upserts are rejected, `findOne`\nstrictness, the opt-in `maxLimit`/`maxBatchSize` caps, `bulkWrite` safety gate).\n","readmeFilename":"README.md"}