{"id":50404045,"url":"https://github.com/kilicdev/kilic.db","last_synced_at":"2026-05-31T01:00:44.973Z","repository":{"id":358568057,"uuid":"1228298747","full_name":"kilicdev/kilic.db","owner":"kilicdev","description":"Zero-boilerplate MongoDB wrapper. Configure once, use everywhere. Singleton-based, race condition proof, and fully typed.","archived":false,"fork":false,"pushed_at":"2026-05-18T01:28:44.000Z","size":29,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-18T02:49:14.009Z","etag":null,"topics":["database","db","kilic","mongodb","mongoose","nosql","orm","singleton","wrapper","zero-boilerplate"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/kilic.db","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/kilicdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-03T21:12:20.000Z","updated_at":"2026-05-18T01:28:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kilicdev/kilic.db","commit_stats":null,"previous_names":["kilicdev/kilic.db"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/kilicdev/kilic.db","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kilicdev%2Fkilic.db","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kilicdev%2Fkilic.db/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kilicdev%2Fkilic.db/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kilicdev%2Fkilic.db/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kilicdev","download_url":"https://codeload.github.com/kilicdev/kilic.db/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kilicdev%2Fkilic.db/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33715211,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"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":["database","db","kilic","mongodb","mongoose","nosql","orm","singleton","wrapper","zero-boilerplate"],"created_at":"2026-05-31T01:00:44.099Z","updated_at":"2026-05-31T01:00:44.956Z","avatar_url":"https://github.com/kilicdev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# kilic.db\n\n### Tiny MongoDB commands for Node.js projects that already love Mongoose.\n\n[![npm version](https://img.shields.io/npm/v/kilic.db.svg?style=for-the-badge)](https://www.npmjs.com/package/kilic.db)\n[![npm downloads](https://img.shields.io/npm/dm/kilic.db.svg?style=for-the-badge)](https://www.npmjs.com/package/kilic.db)\n[![TypeScript](https://img.shields.io/badge/TypeScript-ready-3178c6.svg?style=for-the-badge)](https://www.typescriptlang.org/)\n[![Docs](https://img.shields.io/badge/Docs-GitHub%20Pages-111827.svg?style=for-the-badge)](https://kilicdev.github.io/kilic.db)\n[![License: MIT](https://img.shields.io/badge/License-MIT-111111.svg?style=for-the-badge)](LICENSE)\n\nConfigure once. Query everywhere. Keep the API small. Keep MongoDB powerful.\n\n\u003c/div\u003e\n\n---\n\n## Overview\n\n`kilic.db` is a compact command layer over Mongoose. It gives everyday database work a clean shape without hiding the native MongoDB/Mongoose escape hatches.\n\n```js\nconst db = require(\"kilic.db\");\n\nawait db.create(\"User\", { id: \"u_1\", email: \"ada@example.com\" });\n\nconst user = await db.get(\"User\", { id: \"u_1\" });\n\nawait db.update(\"User\", { $inc: { loginCount: 1 } }, { id: \"u_1\" });\n\nconst revenue = await db.aggregate(\"Order\", [\n  { $match: { status: \"paid\" } },\n  { $group: { _id: \"$currency\", total: { $sum: \"$amount\" } } },\n]);\n```\n\n## Why\n\n| You want | kilic.db gives you |\n|---|---|\n| One database setup | `db.config()` once, then use it anywhere |\n| Clear create/update semantics | `create()` inserts once, `update()` changes existing data |\n| Small surface area | A focused command set instead of a giant wrapper |\n| Safe defaults | Write methods reject empty filters and empty payloads |\n| Real MongoDB power | First-class `aggregate()` plus raw `db.model()` |\n| TypeScript without ceremony | Generic return types where they matter |\n\n## Install\n\n```bash\nnpm install kilic.db mongoose\n```\n\nNode.js `20.19.0` or newer is required.\n\n`mongoose` is a peer dependency, so your app owns the actual Mongoose version.\n\n`archiver` is installed with `kilic.db` and is used internally by `db.backup()`.\n`mongodb-memory-server-core` is also installed with `kilic.db`; it is loaded only when you omit `url`.\n\n## Configure\n\n```js\nconst db = require(\"kilic.db\");\nconst path = require(\"path\");\n\ndb.config({\n  url: \"mongodb://localhost:27017/myapp\",\n  path: path.join(__dirname, \"models\"),\n  backupDir: path.join(__dirname, \"backups\"),\n  debug: true,\n});\n```\n\n`config()` starts the connection in the background. Mongoose buffers commands while the connection is opening.\n\nNo MongoDB server? Omit `url`. kilic.db starts a local one-node `mongodb-memory-server-core` replica set, loads the JSON file into MongoDB, and writes changes back to the same file after kilic.db write commands:\n\n```js\ndb.config({\n  file: path.join(__dirname, \"data\", \"kilic.db.json\"),\n  database: \"myapp\",\n  path: path.join(__dirname, \"models\"),\n});\n```\n\nIf `file` is omitted, kilic.db uses `\u003ccwd\u003e/kilic.db.json`. The file uses MongoDB EJSON, so ObjectIds, dates, and other BSON values can round-trip safely.\n\nThe memory mode uses `mongodb-memory-server-core`, so it does not download a MongoDB binary during package install. The first URL-free run may download the `mongod` binary into the user's MongoDB Memory Server cache unless `MONGOMS_SYSTEM_BINARY` or other MongoDB Memory Server config points to an existing binary.\n\nNeed an explicit boot barrier?\n\n```js\nawait db.ready();\n```\n\nConfig options:\n\n| Option | Type | Purpose |\n|---|---|---|\n| `url` | `string` | MongoDB connection string |\n| `options` | `ConnectOptions` | Options passed to `mongoose.connect()` |\n| `path` | `string` | Directory for auto-loading model files |\n| `file` | `string` | JSON file for built-in memory-server persistence when `url` is omitted |\n| `database` | `string` | Database name for built-in memory-server mode |\n| `memoryServerOptions` | `object` | Options passed to `MongoMemoryReplSet.create()` |\n| `backupDir` | `string` | Default output directory for `db.backup()` |\n| `debug` | `boolean` | Print small kilic.db lifecycle logs |\n\n## Command Map\n\n| Command | Reads like | Supports |\n|---|---|---|\n| `config(options)` | connect and configure | `url`, `file`, `path`, Mongoose connect options |\n| `ready()` | wait for connection | startup checks |\n| `flush()` | write file-backed data now | memory-server mode |\n| `disconnect()` | flush and close | scripts, tests, shutdown |\n| `create(model, data, options?)` | create once | single data, array data, custom filters |\n| `get(model, filter, options?)` | read one | projection, populate, session, lean control |\n| `update(model, data, filter?, options?)` | update data | single update, array updates, `multi` |\n| `delete(model, filter, options?)` | delete data | single filter, array filters, `multi` |\n| `find(model, filter?, options?)` | read many | projection, sort, skip, limit, populate, cursor |\n| `count(model, filter?, options?)` | count many | filtered counts |\n| `aggregate(model, stages, options?)` | run pipeline | full MongoDB aggregation |\n| `backup(options?)` | zip a database backup | EJSON collection dumps, metadata, dated zip files |\n| `model(model)` | escape hatch | raw Mongoose model access |\n\n---\n\n## Create\n\n`create()` means “create this logical document once.” It uses an atomic upsert with `$setOnInsert`, so existing documents are not overwritten.\n\n```js\nawait db.create(\"User\", {\n  id: \"u_1\",\n  email: \"ada@example.com\",\n  name: \"Ada\",\n});\n```\n\nWithout `id`, provide the identity filter:\n\n```js\nawait db.create(\n  \"User\",\n  { email: \"ada@example.com\", name: \"Ada\" },\n  { filter: { email: \"ada@example.com\" } }\n);\n```\n\nCreate many by passing an array:\n\n```js\nawait db.create(\"User\", [\n  { id: \"u_1\", email: \"ada@example.com\" },\n  { id: \"u_2\", email: \"grace@example.com\" },\n]);\n```\n\nCreate many with a filter resolver:\n\n```js\nawait db.create(\"User\", users, {\n  filter: (user) =\u003e ({ email: user.email }),\n});\n```\n\nCreate many with a filter array:\n\n```js\nawait db.create(\"User\", users, {\n  filter: users.map((user) =\u003e ({ email: user.email })),\n});\n```\n\nArray data cannot share one static filter object. This is blocked on purpose:\n\n```js\n// Throws: every item would target the same document.\nawait db.create(\"User\", users, {\n  filter: { email: \"ada@example.com\" },\n});\n```\n\nUse a resolver function or a filter array so every item has its own identity:\n\n```js\nawait db.create(\"User\", users, {\n  filter: (user) =\u003e ({ email: user.email }),\n});\n```\n\n| Guardrail | Why it exists |\n|---|---|\n| Empty data is rejected | A create command should create meaningful data |\n| Update operators are rejected | `$inc`, `$push`, `$set` belong in `update()` |\n| Shared static filters are rejected for array data | Prevents many items from writing the same document |\n| Duplicate key races return existing docs when possible | Startup and request flows stay idempotent |\n\n## Get\n\n```js\nconst user = await db.get(\"User\", { id: \"u_1\" });\n```\n\n```js\nconst publicUser = await db.get(\"User\", { id: \"u_1\" }, {\n  projection: { password: 0, token: 0 },\n});\n```\n\n```js\nconst post = await db.get(\"Post\", { id: \"p_1\" }, {\n  populate: \"author\",\n});\n```\n\nLean objects are returned by default. Ask for a Mongoose document when you need document methods:\n\n```js\nconst userDoc = await db.get(\"User\", { id: \"u_1\" }, {\n  lean: false,\n});\n```\n\n## Update\n\nPlain objects become `$set` updates:\n\n```js\nawait db.update(\"User\", { name: \"Grace\" }, { id: \"u_1\" });\n```\n\nMongoDB update operators pass through:\n\n```js\nawait db.update(\"User\", { $inc: { loginCount: 1 } }, { id: \"u_1\" });\n```\n\nUpdate many matching documents with one payload:\n\n```js\nawait db.update(\"User\", { archived: true }, { active: false }, {\n  multi: true,\n});\n```\n\nUpdate many documents with different payloads:\n\n```js\nawait db.update(\"User\", [\n  { id: \"u_1\", name: \"Ada\" },\n  { id: \"u_2\", name: \"Grace\" },\n]);\n```\n\nUse a filter resolver when your identity field is not `id`:\n\n```js\nawait db.update(\"User\", users, (user) =\u003e ({ email: user.email }));\n```\n\nArray updates also reject one shared filter object:\n\n```js\n// Throws: every update would target the same user.\nawait db.update(\"User\", users, { email: \"ada@example.com\" });\n```\n\nWhen `multi: true` is used, `update()` returns counts instead of a document:\n\n```js\nconst result = await db.update(\"User\", { archived: true }, { active: false }, {\n  multi: true,\n});\n\nconsole.log(result.matchedCount, result.modifiedCount);\n```\n\n## Delete\n\nDelete one:\n\n```js\nawait db.delete(\"Session\", { token: \"session_token\" });\n```\n\nDelete many matching one filter:\n\n```js\nawait db.delete(\"Session\", { expired: true }, { multi: true });\n```\n\nDelete multiple independent filters:\n\n```js\nawait db.delete(\"Session\", [\n  { token: \"token_1\" },\n  { token: \"token_2\" },\n]);\n```\n\n`delete()` returns `{ success, deletedCount }`.\n\n## Find\n\n```js\nconst users = await db.find(\"User\", { active: true }, {\n  projection: { password: 0 },\n  sort: { createdAt: -1 },\n  skip: 20,\n  limit: 10,\n  populate: \"team\",\n});\n```\n\nBy default, `find()` returns an array. That is perfect for normal lists and paginated screens.\n\nFor huge datasets, do not load everything into memory. Use cursor mode:\n\n```js\nconst cursor = await db.find(\"Log\", { level: \"error\" }, {\n  cursor: true,\n  sort: { createdAt: 1 },\n  cursorOptions: { batchSize: 500 },\n});\n\nfor await (const log of cursor) {\n  // process one document at a time\n}\n```\n\nCursor mode returns a Mongoose async iterable instead of an array. It is the right path for exports, migrations, backfills, and large reporting jobs.\n\nFor even more control, raw Mongoose is still available:\n\n```js\nconst cursor = db.model(\"Log\").find({ level: \"error\" }).cursor();\n```\n\n## Count\n\n```js\nconst activeUsers = await db.count(\"User\", { active: true });\n```\n\nNeed a metadata-based estimate?\n\n```js\nconst totalUsers = await db.model(\"User\").estimatedDocumentCount();\n```\n\n## Aggregate\n\nAggregation is a core MongoDB feature, so it is first-class here.\n\n```js\nconst leaderboard = await db.aggregate(\"Score\", [\n  { $match: { season: \"2026\" } },\n  { $group: { _id: \"$userId\", total: { $sum: \"$points\" } } },\n  { $sort: { total: -1 } },\n  { $limit: 10 },\n], {\n  allowDiskUse: true,\n});\n```\n\nSessions work too:\n\n```js\nconst session = await db.mongoose.startSession();\n\nconst rows = await db.aggregate(\"Order\", [\n  { $match: { status: \"paid\" } },\n  { $group: { _id: \"$userId\", revenue: { $sum: \"$amount\" } } },\n], { session });\n```\n\nUse the real pipeline stages: `$lookup`, `$unwind`, `$facet`, `$project`, `$bucket`, `$graphLookup`, and everything else MongoDB supports through Mongoose.\n\n## Backup\n\nCreate a dated zip backup of every collection:\n\n```js\nconst backup = await db.backup();\n\nconsole.log(backup.file);\n```\n\nSet a default backup directory in config:\n\n```js\ndb.config({\n  url: \"mongodb://localhost:27017/myapp\",\n  backupDir: path.join(__dirname, \"backups\"),\n});\n```\n\nOr override it for one run:\n\n```js\nawait db.backup({\n  backupDir: \"/var/backups/myapp\",\n  batchSize: 500,\n});\n```\n\nUse a custom file id when you want a stable name:\n\n```js\nawait db.backup({\n  id: \"before-migration\",\n});\n```\n\n`backup()` returns:\n\n```js\n{\n  success: true,\n  id: \"kilic-db-2026-05-21T16-30-00-000Z\",\n  file: \"/app/backups/kilic-db-2026-05-21T16-30-00-000Z.zip\",\n  directory: \"/app/backups\",\n  database: \"myapp\",\n  collections: [\n    { collection: \"users\", count: 42, file: \"users.json\" },\n  ],\n  size: 12480,\n  createdAt: \"2026-05-21T16:30:00.000Z\",\n}\n```\n\nThe zip contains one EJSON `.json` dump per collection plus `__meta__.json`. Backups are logical JSON exports, not a replacement for MongoDB's native `mongodump` archive format. For very large databases, native MongoDB tooling is still the safer operational choice.\n\n## Raw Mongoose\n\nThe wrapper stays small on purpose. When you need full Mongoose, take the model:\n\n```js\nconst User = db.model(\"User\");\n\nawait User.bulkWrite([\n  {\n    updateOne: {\n      filter: { id: \"u_1\" },\n      update: { $set: { role: \"admin\" } },\n    },\n  },\n]);\n```\n\nRaw access is also available for sessions, plugins, transactions, and connection events:\n\n```js\nconst session = await db.mongoose.startSession();\n\ndb.connection.on(\"disconnected\", () =\u003e {\n  console.warn(\"MongoDB disconnected\");\n});\n```\n\nIn file-backed memory-server mode, kilic.db flushes after its own write commands. If you write through raw Mongoose models directly, call `await db.flush()` before shutdown so those changes are written to the JSON file.\n\n## Examples\n\nThe `examples/` directory contains runnable examples for both modes:\n\n```bash\nnpm run build\nnode examples/file-backed-memory.js\nnode examples/transactions.js\n```\n\nUse `examples/mongodb-url.js` with a real MongoDB URL:\n\n```bash\nMONGODB_URI=\"mongodb://127.0.0.1:27017/kilic_example\" node examples/mongodb-url.js\n```\n\n## Development\n\nRun the full local check:\n\n```bash\nnpm test\n```\n\n`npm test` builds the package and runs the Node.js test suite in `tests/`. CI runs type checks, tests, and package dry-run checks on supported Node.js versions.\n\nReleases are automated from `main`: when tests pass and the `package.json` version is not already published on npm, GitHub Actions publishes the package and creates the matching GitHub release tag. Configure npm Trusted Publishing for this repository, or set an `NPM_TOKEN` repository secret.\n\n---\n\n## Models\n\nRegister models yourself:\n\n```js\nmongoose.model(\"User\", userSchema);\n```\n\nOr let kilic.db load model files from `config.path`:\n\n```text\nmodels/\n  User.js\n  Post.js\n  Order.js\n```\n\nEach file should export a Mongoose model:\n\n```js\nmodule.exports = mongoose.model(\"User\", userSchema);\n```\n\nDefault exports are supported.\n\nModel names are resolved only inside `config.path`; path traversal strings such as `\"../User\"` are ignored.\n\n## TypeScript\n\n```ts\nimport db from \"kilic.db\";\n\ninterface User {\n  id: string;\n  email: string;\n  name: string;\n}\n\nconst user = await db.get\u003cUser\u003e(\"User\", { id: \"u_1\" });\nconst users = await db.find\u003cUser\u003e(\"User\", { active: true });\nconst created = await db.create\u003cUser\u003e(\"User\", {\n  id: \"u_2\",\n  email: \"grace@example.com\",\n});\n```\n\nTyped aggregation results:\n\n```ts\ninterface RevenueRow {\n  _id: string;\n  total: number;\n}\n\nconst rows = await db.aggregate\u003cRevenueRow\u003e(\"Order\", [\n  { $group: { _id: \"$currency\", total: { $sum: \"$amount\" } } },\n]);\n```\n\nTyped backup results:\n\n```ts\nconst backup = await db.backup({\n  backupDir: \"./backups\",\n});\n\nbackup.collections.forEach((item) =\u003e {\n  console.log(item.collection, item.count);\n});\n```\n\n## Errors\n\nAll wrapper errors are `KilicError` instances with a stable `code` field:\n\n```js\ntry {\n  await db.delete(\"User\", {});\n} catch (err) {\n  console.log(err.code);\n  console.log(err.message);\n}\n```\n\nExample message:\n\n```text\n[kilic.db:MISSING_FILTER]\ndelete() requires a non-empty filter.\n```\n\nMongoose duplicate key, validation, and cast errors are normalized with hints and details while preserving `originalError`.\n\n## Safety\n\n| Operation | Guardrail |\n|---|---|\n| `create()` | Rejects empty data and update operators |\n| `create()` | Uses `$setOnInsert` so existing documents are not overwritten |\n| `create(array)` | Rejects one shared filter object; use a resolver or filter array |\n| `update()` | Uses `data.id` or a non-empty `filter` |\n| `update(array)` | Rejects one shared filter object; use a resolver or filter array |\n| `update({ multi: true })` | Requires one explicit filter object |\n| `delete()` | Requires non-empty filters |\n| `find({ cursor: true })` | Streams results instead of building a huge array |\n| `aggregate()` | Requires an array pipeline |\n| `backup()` | Writes to a temporary folder first, then zips and cleans it up |\n\nThese guardrails are not a security product. They are boring defaults that prevent the common foot-guns.\n\n## Recipes\n\n### Idempotent Registration\n\n```js\nawait db.create(\"User\", {\n  id: externalUser.id,\n  email: externalUser.email,\n  provider: \"github\",\n});\n```\n\n### Batch Sync\n\n```js\nawait db.create(\"Customer\", customers, {\n  filter: (customer) =\u003e ({ externalId: customer.externalId }),\n});\n\nawait db.update(\"Customer\", customers, (customer) =\u003e ({\n  externalId: customer.externalId,\n}));\n```\n\n### Huge Export\n\n```js\nconst cursor = await db.find(\"Event\", { type: \"purchase\" }, {\n  cursor: true,\n  sort: { createdAt: 1 },\n});\n\nfor await (const event of cursor) {\n  await writeToExport(event);\n}\n```\n\n### Archive Old Data\n\n```js\nawait db.update(\n  \"User\",\n  { archived: true },\n  { lastLoginAt: { $lt: new Date(\"2025-01-01\") } },\n  { multi: true }\n);\n```\n\n### Dashboard Stats\n\n```js\nconst stats = await db.aggregate(\"Order\", [\n  { $match: { status: \"paid\" } },\n  {\n    $group: {\n      _id: {\n        day: { $dateToString: { format: \"%Y-%m-%d\", date: \"$createdAt\" } },\n      },\n      orders: { $sum: 1 },\n      revenue: { $sum: \"$amount\" },\n    },\n  },\n  { $sort: { \"_id.day\": 1 } },\n], { allowDiskUse: true });\n```\n\n---\n\n## Philosophy\n\n`kilic.db` is not trying to replace Mongoose. It is the small layer you write when you are tired of repeating the same database ceremony across routes, services, jobs, and scripts.\n\n```text\ncreate    create one or many logical documents once\nget       read one document\nupdate    update one, array data, or many with multi\ndelete    delete one, array filters, or many with multi\nfind      read many documents\ncount     count matching documents\naggregate run a MongoDB pipeline\nbackup   create a dated EJSON zip backup\nmodel     use raw Mongoose\n```\n\nIf a feature is common and benefits from a clear command, it belongs here. If a feature is broad, rare, or deeply Mongo-specific, `db.model()` keeps it one line away.\n\n## License\n\nMIT © [kilicdev](https://github.com/kilicdev)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkilicdev%2Fkilic.db","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkilicdev%2Fkilic.db","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkilicdev%2Fkilic.db/lists"}