{"id":48359559,"url":"https://github.com/monarch-orm/monarch","last_synced_at":"2026-04-08T20:03:50.032Z","repository":{"id":241849338,"uuid":"805529558","full_name":"monarch-orm/monarch","owner":"monarch-orm","description":"Monarch ORM: A type-safe Object Document Mapper (ODM) for MongoDB","archived":false,"fork":false,"pushed_at":"2026-04-05T10:08:38.000Z","size":878,"stargazers_count":28,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-04-05T12:24:41.008Z","etag":null,"topics":["mongodb","mongoose","odm","orm","typesafe","typescript"],"latest_commit_sha":null,"homepage":"","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/monarch-orm.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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":"2024-05-24T19:28:18.000Z","updated_at":"2026-04-05T10:07:40.000Z","dependencies_parsed_at":"2025-03-24T12:32:01.971Z","dependency_job_id":"88c0365e-6b3c-4fba-ab2b-19d70f758621","html_url":"https://github.com/monarch-orm/monarch","commit_stats":null,"previous_names":["princecodes247/monarch","monarch-orm/monarch"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/monarch-orm/monarch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monarch-orm%2Fmonarch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monarch-orm%2Fmonarch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monarch-orm%2Fmonarch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monarch-orm%2Fmonarch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/monarch-orm","download_url":"https://codeload.github.com/monarch-orm/monarch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/monarch-orm%2Fmonarch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31571601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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","odm","orm","typesafe","typescript"],"created_at":"2026-04-05T12:01:15.279Z","updated_at":"2026-04-08T20:03:50.015Z","avatar_url":"https://github.com/monarch-orm.png","language":"TypeScript","funding_links":[],"categories":["Libraries"],"sub_categories":["JavaScript"],"readme":"# Monarch ORM\n\nType-safe MongoDB collections, schema parsing, relations, and query helpers for TypeScript.\n\n## Features\n\n- **Strongly Typed:** Infer schema inputs and outputs for queries and collection methods.\n- **Flexible Schemas:** Use transforms, defaults, validation, virtuals, renames, and default omit rules.\n- **Typed Relations:** Define one, many, and refs relations with typed population support.\n- **Familiar MongoDB Access:** Use typed query methods, operators, aggregation, and raw collection access.\n- **Collection Initialization:** Automatically initialize collections with indexes and JSON Schema validation, or do it manually.\n\n## Installation\n\n```bash\nnpm install monarch-orm\n```\n\n## Quick Start\n\n```ts\nimport {\n  createClient,\n  createDatabase,\n  createSchema,\n  defineSchemas,\n} from \"monarch-orm\";\nimport { boolean, number, string } from \"monarch-orm/types\";\n\n// Define collection schema.\nconst userSchema = createSchema(\"users\", {\n  name: string().trim(),\n  email: string().lowercase(),\n  age: number().integer().min(13).optional(),\n  isVerified: boolean().default(false),\n});\n\nconst schemas = defineSchemas({ userSchema });\n\n// Create and connect the MongoDB client.\nconst client = createClient(process.env.MONGODB_URI!);\nawait client.connect();\n\n// Create a database instance.\nconst db = createDatabase(client.db(\"app\"), schemas);\n\n// Insert one document.\nconst user = await db.collections.users.insertOne({\n  name: \"Alice\",\n  email: \"alice@example.com\",\n});\n\n// Query documents.\nconst users = await db.collections.users\n  .find({ isVerified: false })\n  .select({ name: true, email: true })\n  .sort({ email: \"asc\" });\n```\n\n## Schemas\n\n`createSchema()` defines a collection's shape. If you do not define `_id`, it defaults to `objectId()`. When a schema uses `ObjectId` for `_id`, Monarch makes the input optional for inserts.\n\n```ts\nimport { createSchema } from \"monarch-orm\";\nimport { array, date, object, objectId, string } from \"monarch-orm/types\";\n\nconst postSchema = createSchema(\"posts\", {\n  title: string().trim().nonempty(),\n  body: string(),\n  authorId: objectId(),\n  contributorIds: array(objectId()).default([]),\n  metadata: object({\n    slug: string().lowercase(),\n  }),\n  publishedAt: date().optional(),\n});\n```\n\n`defineSchemas()` normalizes schemas keyed by collection name. It also holds schema relations.\n\n```ts\nconst schemas = defineSchemas({ userSchema, postSchema });\n```\n\n### Relations\n\nUse `withRelations()` on a schemas object to define typed relations.\n\n```ts\nconst schemas = defineSchemas({ userSchema, postSchema });\n\nconst schemasWithRelations = schemas.withRelations((s) =\u003e ({\n  users: {\n    posts: s.users.$many.posts({ from: \"_id\", to: \"authorId\" }),\n  },\n  posts: {\n    author: s.posts.$one.users({ from: \"authorId\", to: \"_id\" }),\n    contributors: s.posts.$refs.users({ from: \"contributorIds\", to: \"_id\" }),\n  },\n}));\n```\n\n#### One relations\n\nUse `$one` when a local field points to a single document in another collection.\n\n```ts\nauthor: s.posts.$one.users({ from: \"authorId\", to: \"_id\" })\n```\n\n#### Many relations\n\nUse `$many` when one document relates to many documents in another collection by matching a local field against a foreign field.\n\n```ts\nposts: s.users.$many.posts({ from: \"_id\", to: \"authorId\" })\n```\n\n#### Refs relations\n\nUse `$refs` when a local array field stores multiple references to another collection.\n\n```ts\ncontributors: s.posts.$refs.users({ from: \"contributorIds\", to: \"_id\" })\n```\n\n### Schema Groups\n\nYou can split schemas by concern or by file, define relations inside each group, then merge them together. This works well when different modules own different parts of your data model.\n\n```ts\nimport { createSchema, defineSchemas, mergeSchemas } from \"monarch-orm\";\nimport { objectId, string } from \"monarch-orm/types\";\n\nconst userSchema = createSchema(\"users\", {\n  name: string(),\n  tutorId: objectId().optional(),\n});\n\nconst userGroup = defineSchemas({ userSchema }).withRelations((s) =\u003e ({\n  users: {\n    tutor: s.users.$one.users({ from: \"tutorId\", to: \"_id\" }),\n  },\n}));\n\nconst postSchema = createSchema(\"posts\", {\n  title: string(),\n  authorId: objectId(),\n});\n\nconst categorySchema = createSchema(\"categories\", {\n  name: string(),\n  parentId: objectId().optional(),\n});\n\nconst contentGroup = defineSchemas({ postSchema, categorySchema }).withRelations((s) =\u003e ({\n  categories: {\n    parent: s.categories.$one.categories({ from: \"parentId\", to: \"_id\" }),\n  },\n}));\n\nconst schemas = mergeSchemas(userGroup, contentGroup);\n```\n\nYou can also add cross-group relations after merging:\n\n```ts\nconst schemasWithCrossGroupRelations = schemas.withRelations((s) =\u003e ({\n  users: {\n    posts: s.users.$many.posts({ from: \"_id\", to: \"authorId\" }),\n  },\n  posts: {\n    author: s.posts.$one.users({ from: \"authorId\", to: \"_id\" }),\n  },\n}));\n```\n\n### Databases\n\nPass schemas object into `createDatabase()` to create db with typed collections.\n\n```ts\nconst schemas = defineSchemas({ userSchema, postSchema });\n\nconst db = createDatabase(client.db(\"app\"), schemas);\nawait db.isReady;\n```\n\nBy default, `createDatabase()` initializes all collections. If you want to do that manually, disable initialization and call `db.initialize()` yourself. `initialize()` can be configured and can target only selected schemas.\n\n```ts\nconst db = createDatabase(client.db(\"app\"), schemas, {\n  initialize: false,\n});\n\nawait db.initialize({\n  indexes: true,\n  validation: true,\n  collections: {\n    users: true,\n  },\n});\n```\n\n`db.isReady` resolves when database initialization has finished. Each collection also has its own `isReady`, for example `db.collections.users.isReady`.\n\n## Queries\n\nCollections expose typed query methods.\n\n### `insertOne(data)`\n\nInserts one document after parsing it through the schema.\n\n```ts\nconst user = await db.collections.users.insertOne({\n  name: \"Alice\",\n  email: \"alice@example.com\",\n});\n```\n\n### `insertMany(data[])`\n\nInserts multiple documents after parsing each one through the schema.\n\n```ts\nawait db.collections.users.insertMany([\n  { name: \"Grace\", email: \"grace@example.com\" },\n  { name: \"Linus\", email: \"linus@example.com\" },\n]);\n```\n\n### `find(filter?)`\n\nReturns a query for multiple documents. It supports `select()`, `omit()`, `sort()`, `limit()`, `skip()`, `options()`, `cursor()`, and `populate()`.\n\n```ts\nconst allUsers = await db.collections.users.find();\n\nconst verifiedUsers = await db.collections.users\n  .find({ isVerified: true })\n  .omit({ age: true })\n  .limit(20)\n  .skip(10)\n  .sort({ email: \"asc\" });\n\nconst cursor = await db.collections.users.find({ isVerified: true }).cursor();\nfor await (const item of cursor) {\n  console.log(item.email);\n}\n```\n\nIf your schema has relations, `find()` can populate them:\n\n```ts\nconst posts = await db.collections.posts.find().populate({\n  author: true,\n  contributors: true,\n});\n```\n\nPopulate options support nested `populate`, plus `select`, `omit`, `sort`, `skip`, and `limit` on the populated query.\n\n```ts\nconst users = await db.collections.users.find().populate({\n  posts: {\n    sort: { title: -1 },\n    limit: 5,\n    populate: {\n      author: true,\n    },\n  },\n});\n```\n\n### `findOne(filter)`\n\nReturns a query for a single document. It supports `select()`, `omit()`, `options()`, and `populate()`.\n\n```ts\nconst user = await db.collections.users.findOne({ email: \"alice@example.com\" });\n```\n\n### `findById(id)`\n\nReturns a query for a single document by `_id`. For `objectId()` schemas, it accepts either an `ObjectId` or a valid ObjectId string.\n\n```ts\nconst byId = await db.collections.users.findById(\"67f0123456789abcdef0123\");\n```\n\n### `updateOne(filter, update)`\n\nUpdates one matching document. It supports `options()`.\n\n```ts\nawait db.collections.users.updateOne(\n  { email: \"alice@example.com\" },\n  { $set: { isVerified: true } },\n);\n```\n\n### `updateMany(filter, update)`\n\nUpdates all matching documents. It supports `options()`.\n\n```ts\nawait db.collections.users.updateMany(\n  { isVerified: false },\n  { $set: { age: 18 } },\n);\n```\n\n### `findOneAndUpdate(filter, update)`\n\nUpdates one document and returns the matched document by default, or the updated one when configured with `options({ returnDocument: \"after\" })`. It also supports `select()`, `omit()`, and `options()`.\n\n```ts\nconst updated = await db.collections.users\n  .findOneAndUpdate(\n    { email: \"alice@example.com\" },\n    { $set: { isVerified: true } },\n  )\n  .options({ returnDocument: \"after\" });\n```\n\n### `findByIdAndUpdate(id, update)`\n\nLike `findOneAndUpdate()`, but matches by `_id`.\n\n```ts\nconst updated = await db.collections.users\n  .findByIdAndUpdate(\"67f0123456789abcdef0123\", {\n    $set: { isVerified: true },\n  })\n  .options({ returnDocument: \"after\" });\n```\n\nSchema parsing still runs for update input, so transforms like `.lowercase()` and validators still apply inside `$set`.\n\n### `replaceOne(filter, replacement)`\n\nReplaces one matching document. It supports `options()`.\n\n```ts\nawait db.collections.users.replaceOne(\n  { email: \"alice@example.com\" },\n  { name: \"Alice Lovelace\", email: \"alice@example.com\" },\n);\n```\n\n### `findOneAndReplace(filter, replacement)`\n\nReplaces one document and returns the matched document by default, or the replacement when configured with `options({ returnDocument: \"after\" })`. It also supports `select()`, `omit()`, and `options()`.\n\n```ts\nconst replaced = await db.collections.users\n  .findOneAndReplace(\n    { email: \"alice@example.com\" },\n    { name: \"Alice\", email: \"alice@example.com\" },\n  )\n  .options({ returnDocument: \"after\" });\n```\n\n### `deleteOne(filter)`\n\nDeletes one matching document.\n\n```ts\nawait db.collections.users.deleteOne({ email: \"alice@example.com\" });\n```\n\n### `deleteMany(filter)`\n\nDeletes all matching documents.\n\n```ts\nawait db.collections.users.deleteMany({ isVerified: false });\n```\n\n### `findOneAndDelete(filter)`\n\nDeletes one matching document and returns it.\n\n```ts\nconst deleted = await db.collections.users.findOneAndDelete({\n  email: \"alice@example.com\",\n});\n```\n\n### `findByIdAndDelete(id)`\n\nDeletes one document by `_id` and returns it.\n\n```ts\nconst deleted = await db.collections.users.findByIdAndDelete(\"67f0123456789abcdef0123\");\n```\n\n### Other Collection Methods\n\n### `distinct(key, filter?)`\n\nReturns a query for the distinct values of a field.\n\n```ts\nconst emails = await db.collections.users.distinct(\"email\", { isVerified: true });\n```\n\n### `bulkWrite(operations)`\n\nRuns multiple MongoDB bulk write operations.\n\n```ts\nawait db.collections.users.bulkWrite([\n  {\n    insertOne: {\n      document: {\n        name: \"Alice\",\n        email: \"alice@example.com\",\n      },\n    },\n  },\n  {\n    updateOne: {\n      filter: { email: \"alice@example.com\" },\n      update: { $set: { isVerified: true } },\n    },\n  },\n]);\n```\n\n### `countDocuments(filter?, options?)`\n\nCounts matching documents.\n\n```ts\nconst verifiedCount = await db.collections.users.countDocuments({ isVerified: true });\n```\n\n### `estimatedDocumentCount(options?)`\n\nReturns MongoDB's estimated document count for the collection.\n\n```ts\nconst totalCount = await db.collections.users.estimatedDocumentCount();\n```\n\n### `aggregate()`\n\nBuilds an aggregation pipeline.\n\n```ts\nconst result = await db.collections.users\n  .aggregate()\n  .addStage({ $match: { isVerified: true } })\n  .addStage({ $group: { _id: \"$isVerified\", count: { $sum: 1 } } });\n```\n\n### `raw()`\n\nReturns the underlying MongoDB collection.\n\n```ts\nconst rawUsers = await db.collections.users.raw().find().toArray();\n```\n\nQueries are lazy, so you can build and reuse them before execution. They run only when you `await` them or call a promise method like `.then()`, `.catch()`, or `.finally()`.\n\n```ts\nlet verifiedUsersQuery = db.collections.users\n  .find({ isVerified: true })\n  .omit({ age: true })\n  .sort({ email: \"asc\" });\n\nif (limitResults) {\n  verifiedUsersQuery = verifiedUsersQuery.limit(10);\n}\n\nconst verifiedUsers = await verifiedUsersQuery;\n```\n\n### Schema features\n\nThese schema methods control default output behavior, initialization behavior, and automatic write-time behavior.\n\n#### Omit fields from output\n\n`schema.omit()` defines the default output projection for that schema. Query-level `.select()` or `.omit()` overrides that default for the current query.\n\n```ts\nconst userSchema = createSchema(\"users\", {\n  name: string(),\n  passwordHash: string(),\n}).omit({\n  passwordHash: true,\n});\n```\n\n#### Virtual fields\n\n`schema.virtuals()` adds computed output fields. Virtuals are not stored in MongoDB, but they are available in query results and can depend on omitted source fields.\n\n```ts\nimport { createSchema, virtual } from \"monarch-orm\";\nimport { boolean, string } from \"monarch-orm/types\";\n\nconst userSchema = createSchema(\"users\", {\n  isAdmin: boolean(),\n  firstName: string(),\n  lastName: string(),\n}).virtuals({\n  role: virtual(\"isAdmin\", ({ isAdmin }) =\u003e (isAdmin ? \"admin\" : \"user\")),\n  fullName: virtual([\"firstName\", \"lastName\"], ({ firstName, lastName }) =\u003e `${firstName} ${lastName}`),\n});\n```\n\n#### Rename output fields\n\n`schema.rename()` changes field names in query output without changing how the field is stored in MongoDB.\n\n```ts\nconst userSchema = createSchema(\"users\", {\n  name: string(),\n}).rename({\n  _id: \"id\",\n  name: \"fullName\",\n});\n```\n\n#### Indexes\n\n`schema.indexes()` declares the indexes Monarch should keep in sync during collection initialization.\n\n```ts\nconst userSchema = createSchema(\"users\", {\n  email: string().lowercase(),\n  name: string(),\n}).indexes(({ createIndex, unique }) =\u003e ({\n  email: unique(\"email\"),\n  name: createIndex({ name: 1 }),\n}));\n```\n\n#### Collection validation\n\n`schema.validation()` defines validation settings for the schema. Validation can also be set on the database, where it acts as a default for all schemas.\n\n```ts\nconst userSchema = createSchema(\"users\", {\n  email: string().lowercase(),\n}).validation({\n  validationLevel: \"strict\",\n  validationAction: \"error\",\n});\n```\n\nYou can also set the default validation policy at the database level:\n\n```ts\nconst schemas = defineSchemas({ userSchema });\n\nconst db = createDatabase(client.db(\"app\"), schemas, {\n  validation: {\n    validationLevel: \"strict\",\n    validationAction: \"error\",\n  },\n});\n```\n\n#### Automatic update fields\n\n`schema.onUpdate()` injects update operators into every update query for that schema. This is useful for fields like `updatedAt`.\n\n```ts\nimport { date } from \"monarch-orm/types\";\n\nconst userSchema = createSchema(\"users\", {\n  updatedAt: date().optional(),\n}).onUpdate(() =\u003e ({\n  $set: {\n    updatedAt: new Date(),\n  },\n}));\n```\n\n## Types\n\nMonarch ships many ready-made types, but those are not the only possible types. The type system is extensible, and users can create custom types by extending `MonarchType`.\n\n### `string()`\n\nParses strings and supports helpers like `.trim()`, `.lowercase()`, `.uppercase()`, `.nonempty()`, `.minLength()`, `.maxLength()`, and `.pattern()`.\n\n```ts\nconst username = string().trim().lowercase().minLength(3);\n```\n\n### `number()`\n\nParses JavaScript numbers and supports `.min()`, `.max()`, and `.integer()`.\n\n```ts\nconst age = number().integer().min(0);\n```\n\n### `boolean()`\n\nParses booleans.\n\n```ts\nconst isVerified = boolean();\n```\n\n### `date()`\n\nParses `Date` values and supports `.before()`, `.after()`, and `.auto()`.\n\nNote: `.auto()` sets the default value for the type to `new Date()`.\n\n```ts\nconst createdAt = date().auto();\n```\n\n### `objectId()`\n\nParses MongoDB `ObjectId` values and valid `ObjectId` strings.\n\n```ts\nconst authorId = objectId();\n```\n\n### `uuid()`\n\nParses MongoDB `UUID` values and UUID strings, and supports `.auto()`.\n\nNote: `.auto()` sets the default value for the type to `crypto.randomUUID()`.\n\n```ts\nconst sessionId = uuid().auto();\n```\n\n### `regex()`\n\nParses `RegExp` and BSON regex values.\n\n```ts\nconst pattern = regex();\n```\n\n### `binary()`\n\nParses MongoDB binary values.\n\n```ts\nconst fileData = binary();\n```\n\n### `int32()`\n\nParses BSON `Int32` values.\n\n```ts\nconst version = int32();\n```\n\n### `double()`\n\nParses BSON `Double` values.\n\n```ts\nconst score = double();\n```\n\n### `long()`\n\nParses BSON `Long` values.\n\n```ts\nconst totalViews = long();\n```\n\n### `decimal128()`\n\nParses BSON `Decimal128` values.\n\n```ts\nconst amount = decimal128();\n```\n\n### `object(shape)`\n\nCreates nested typed objects and rejects unknown fields.\n\n```ts\nconst profile = object({\n  bio: string(),\n  website: string(),\n});\n```\n\n### `array(type)`\n\nCreates a typed array of values.\n\n```ts\nconst tags = array(string());\n```\n\n### `tuple([...types])`\n\nCreates a fixed-length array with positional types.\n\n```ts\nconst coordinates = tuple([number(), number()]);\n```\n\n### `record(type)`\n\nCreates a string-keyed object whose values all share the same type.\n\n```ts\nconst scores = record(number());\n```\n\n### `literal(...values)`\n\nLimits a field to an exact set of primitive values.\n\n```ts\nconst role = literal(\"admin\", \"editor\", \"member\");\n```\n\n### `union(...types)`\n\nAccepts multiple unrelated type variants.\n\n```ts\nconst phoneOrEmail = union(string(), number());\n```\n\n### `taggedUnion({ ...variants })`\n\nCreates discriminated unions using a `{ tag, value }` object shape.\n\n```ts\nconst notification = taggedUnion({\n  email: object({\n    subject: string(),\n    body: string(),\n  }),\n  sms: object({\n    message: string(),\n  }),\n});\n```\n\n### `mixed()`\n\nAccepts arbitrary values when you need to opt out of strict typing for a field.\n\n```ts\nconst metadata = mixed();\n```\n\n## Modifiers\n\nModifiers let you adapt any type to the exact input and output behavior you want.\n\n### `.optional()`\n\nAllows a field to be omitted.\n\n```ts\nimport { optional } from \"monarch-orm/types\";\n\nconst nickname = string().optional();\n// or functional style\nconst nickname2 = optional(string());\n```\n\n### `.nullable()`\n\nAllows `null`.\n\n```ts\nimport { nullable } from \"monarch-orm/types\";\n\nconst middleName = string().nullable();\n// or functional style\nconst middleName2 = nullable(string());\n```\n\n### `.default(value | fn)`\n\nProvides a fallback when the input is `undefined`.\n\n```ts\nimport { defaulted } from \"monarch-orm/types\";\n\nconst isVerified = boolean().default(false);\n// or functional style\nconst isVerified2 = defaulted(boolean(), false);\n```\n\n### `.validate(fn, message)`\n\nAdds validation after the base type has parsed successfully.\n\n```ts\nconst username = string().validate((value) =\u003e value !== \"admin\", \"username is reserved\");\n```\n\nYou can also use the exported namespace object if you prefer `m.string()` style:\n\n```ts\nimport { createSchema, m } from \"monarch-orm\";\n\nconst userSchema = createSchema(\"users\", {\n  name: m.string(),\n  age: m.number().optional(),\n});\n```\n\n## Operators\n\nMonarch exports typed operator helpers from `monarch-orm/operators`.\n\n```ts\nimport { and, eq, gt, inArray } from \"monarch-orm/operators\";\n\nconst users = await db.collections.users.find(\n  and(\n    { isVerified: eq(true) },\n    { age: gt(18) },\n    { email: inArray([\"alice@example.com\", \"grace@example.com\"]) },\n  ),\n);\n```\n\nAvailable helpers:\n\n- `and`\n- `or`\n- `nor`\n- `not`\n- `eq`\n- `neq`\n- `gt`\n- `gte`\n- `lt`\n- `lte`\n- `inArray`\n- `notInArray`\n- `exists`\n- `notExists`\n- `size`\n\n## Aggregation and Raw Access\n\nUse `aggregate()` for pipeline-based reads:\n\n```ts\nconst result = await db.collections.users\n  .aggregate()\n  .addStage({ $match: { isVerified: true } })\n  .addStage({ $group: { _id: \"$isVerified\", count: { $sum: 1 } } });\n```\n\nUse `raw()` when you need the underlying MongoDB collection:\n\n```ts\nconst rawUsers = await db.collections.users.raw().find().toArray();\n```\n\n## Type Helpers\n\nMonarch exports helper types for inferring collection-level input and output types from a database instance.\n\n### `InferInput\u003ctypeof db, \"collectionName\"\u003e`\n\nInfers the input type for a collection from a database instance.\n\n```ts\nimport type { InferInput } from \"monarch-orm\";\n\ntype UserInsert = InferInput\u003ctypeof db, \"users\"\u003e;\n```\n\n### `InferOutput\u003ctypeof db, \"collectionName\"\u003e`\n\nInfers the default output type for a collection from a database instance.\n\n```ts\nimport type { InferOutput } from \"monarch-orm\";\n\ntype UserResult = InferOutput\u003ctypeof db, \"users\"\u003e;\n```\n\n### `InferOutput\u003ctypeof db, \"collectionName\", options\u003e`\n\nYou can also model projected or populated output shapes by passing options as the third type argument.\n\n```ts\nimport type { InferOutput } from \"monarch-orm\";\n\ntype UserWithPosts = InferOutput\u003c\n  typeof db,\n  \"users\",\n  {\n    populate: {\n      posts: {\n        populate: {\n          author: true;\n        };\n      };\n    };\n  }\n\u003e;\n```\n\n## Utilities\n\n- `ObjectId` is re-exported from `mongodb`\n- `toObjectId()` converts values to `ObjectId`\n- `createClient(uri, options?)` creates a MongoDB client\n- `getValidator(schema)` returns the generated `$jsonSchema` validator\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonarch-orm%2Fmonarch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmonarch-orm%2Fmonarch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmonarch-orm%2Fmonarch/lists"}