Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/abejarano/ts-mongo-criteria/llms.txt

Use this file to discover all available pages before exploring further.

The @abejarano/ts-mongodb-criteria library ships a set of Domain-Driven Design (DDD) primitives that go hand-in-hand with its MongoDB query builder. These classes give you a type-safe foundation for modeling your domain: wrap every scalar field in a ValueObject, define bounded enumerations with EnumValueObject, anchor your aggregate roots with AggregateRoot, and catch validation failures through InvalidArgumentError. All primitives are tree-shakeable and have zero runtime dependencies beyond the Node.js standard library.

Imports

import { ValueObject, StringValueObject, EnumValueObject } from "@abejarano/ts-mongodb-criteria"
import { AggregateRoot, InvalidArgumentError } from "@abejarano/ts-mongodb-criteria"
All symbols are re-exported from the package root, so a single import statement is enough regardless of which primitives you need.

ValueObject<T>

ValueObject<T> is the abstract base class for every domain primitive. It wraps a single scalar value and enforces structural equality — two ValueObject instances are equal when they share the same constructor name and the same underlying value.

Type parameter

ParameterConstraintDescription
TString | string | number | Boolean | boolean | DateThe primitive type stored inside the value object.
The Primitives union is also exported so you can reference it in your own generic constraints:
import { Primitives } from "@abejarano/ts-mongodb-criteria"

Members

MemberSignatureDescription
valuereadonly TThe wrapped primitive. Set once in the constructor and never mutated.
equals(other: ValueObject<T>): booleanReturns true when other has the same constructor name and the same value. Safe to call across class hierarchies.
toString(): stringReturns the string representation of value. Useful for logging and serialisation.

Validation

The constructor calls a private ensureValueIsDefined guard. If you pass undefined as the value — even when TypeScript’s type system might not catch it at the call-site — the constructor throws an InvalidArgumentError immediately, preventing a partially constructed object from escaping into your domain.

Example — custom value object

import { ValueObject, InvalidArgumentError } from "@abejarano/ts-mongodb-criteria"

class ProductSku extends ValueObject<string> {
  // Pattern: SKU-XXXXX (five alphanumeric characters)
  private static readonly SKU_PATTERN = /^SKU-[A-Z0-9]{5}$/

  constructor(value: string) {
    super(value)
    if (!ProductSku.SKU_PATTERN.test(value)) {
      throw new InvalidArgumentError(`"${value}" is not a valid SKU.`)
    }
  }

  static create(value: string): ProductSku {
    return new ProductSku(value)
  }
}

const sku = ProductSku.create("SKU-AB123")
console.log(sku.value)       // "SKU-AB123"
console.log(sku.toString())  // "SKU-AB123"

const other = ProductSku.create("SKU-AB123")
console.log(sku.equals(other)) // true

StringValueObject

StringValueObject extends ValueObject<string> and adds a factory method and a non-empty guard. Use it as the base class for any domain field whose natural type is a non-empty string — identifiers, names, labels, slugs, and so on. Within the library itself, both FilterField and OrderBy extend StringValueObject, which means any class you build on top of it is directly compatible with the query builder.

Members

MemberSignatureDescription
createstatic (value: string): StringValueObjectFactory that constructs and returns a StringValueObject. Prefer over new for consistent error semantics.
getValue(): stringReturns the wrapped string value. Equivalent to reading .value directly.

Validation

In addition to the undefined check inherited from ValueObject, the StringValueObject constructor throws InvalidArgumentError when the supplied string is empty (""). This makes it impossible to model a domain field whose invariant is “must have a value” with an accidentally blank string.

Example — domain fields extending StringValueObject

import { StringValueObject } from "@abejarano/ts-mongodb-criteria"

// A non-empty category slug
class CategorySlug extends StringValueObject {
  static create(value: string): CategorySlug {
    return new CategorySlug(value)
  }
}

// A non-empty product name
class ProductName extends StringValueObject {
  static create(value: string): ProductName {
    return new ProductName(value)
  }
}

const slug = CategorySlug.create("electronics")
const name = ProductName.create("Wireless Headphones")

console.log(slug.getValue()) // "electronics"
console.log(name.getValue()) // "Wireless Headphones"

// Structural equality is type-aware — different classes never equal each other
// even when the underlying string is the same:
console.log(slug.equals(name)) // false

EnumValueObject<T>

EnumValueObject<T> is the abstract base class for domain types that represent a closed set of valid values — order statuses, payment methods, user roles, and similar bounded enumerations. The constructor receives both the chosen value and the complete list of valid values, and calls checkValueIsValid to reject anything outside that list.

Constructor

constructor(value: T, public readonly validValues: T[])
ParameterDescription
valueThe value to store. Must be a member of validValues.
validValuesThe exhaustive list of accepted values. Exposed as a readonly property so callers can introspect the enum’s domain.

Members

MemberSignatureDescription
valuereadonly TThe wrapped enum value.
validValuesreadonly T[]The list of valid values passed to the constructor.
checkValueIsValid(value: T): voidThrows throwErrorForInvalidValue when value is not in validValues. Called automatically by the constructor.
throwErrorForInvalidValueprotected abstract (value: T): voidMust be implemented by every subclass. Called with the offending value when validation fails. Throw the domain error that makes sense for your context.

Example — StatusValueObject

import { EnumValueObject, InvalidArgumentError } from "@abejarano/ts-mongodb-criteria"

type Status = "pending" | "active" | "suspended" | "deleted"

const ALL_STATUSES: Status[] = ["pending", "active", "suspended", "deleted"]

class StatusValueObject extends EnumValueObject<Status> {
  constructor(value: Status) {
    super(value, ALL_STATUSES)
  }

  static create(value: string): StatusValueObject {
    // Validate before casting so the error message is friendly
    if (!ALL_STATUSES.includes(value as Status)) {
      throw new InvalidArgumentError(
        `"${value}" is not a valid status. Expected one of: ${ALL_STATUSES.join(", ")}.`
      )
    }
    return new StatusValueObject(value as Status)
  }

  protected throwErrorForInvalidValue(value: Status): void {
    throw new InvalidArgumentError(
      `"${value}" is not a valid status. Expected one of: ${this.validValues.join(", ")}.`
    )
  }
}

const status = StatusValueObject.create("active")
console.log(status.value)        // "active"
console.log(status.validValues)  // ["pending", "active", "suspended", "deleted"]

AggregateRoot

AggregateRoot is the abstract base class for your aggregate root entities — the top-level objects in your DDD aggregates that map directly to MongoDB documents. It declares the serialisation contract every entity must fulfil, and provides a fromPrimitives factory that subclasses override to reconstruct a fully typed entity from a raw document.

Members

MemberSignatureDescription
getIdabstract (): string | undefinedReturns the entity’s unique identifier, or undefined if the entity has not yet been persisted.
toPrimitivesabstract (): anySerialises the entity to a plain JavaScript object suitable for storing in MongoDB.
fromPrimitivesstatic (data: any): AggregateRootBase implementation throws Error("must implement"). Override in every subclass to reconstruct an entity from a raw document.

AggregateRootClass<T> type

type AggregateRootClass<T extends AggregateRoot> = { fromPrimitives(data: any): T }
This utility type represents the constructor side of an aggregate root class — i.e., the class itself (not an instance). Use it when you need to pass a repository or mapper the class reference rather than an instance:
function hydrateAll<T extends AggregateRoot>(
  docs: any[],
  cls: AggregateRootClass<T>
): T[] {
  return docs.map((doc) => cls.fromPrimitives(doc))
}

Example — full User entity

import { AggregateRoot } from "@abejarano/ts-mongodb-criteria"
import { StringValueObject } from "@abejarano/ts-mongodb-criteria"

// Value objects for User fields
class UserId extends StringValueObject {
  static create(value: string) { return new UserId(value) }
}
class UserEmail extends StringValueObject {
  static create(value: string) { return new UserEmail(value) }
}
class UserName extends StringValueObject {
  static create(value: string) { return new UserName(value) }
}

interface UserPrimitives {
  id: string
  email: string
  name: string
}

class User extends AggregateRoot {
  constructor(
    private readonly id: UserId,
    private readonly email: UserEmail,
    private readonly name: UserName
  ) {
    super()
  }

  // 1. Reconstruct from a raw MongoDB document
  static fromPrimitives(data: UserPrimitives): User {
    return new User(
      UserId.create(data.id),
      UserEmail.create(data.email),
      UserName.create(data.name)
    )
  }

  // 2. Return the entity's identity
  getId(): string | undefined {
    return this.id.getValue()
  }

  // 3. Serialise for persistence
  toPrimitives(): UserPrimitives {
    return {
      id: this.id.getValue(),
      email: this.email.getValue(),
      name: this.name.getValue(),
    }
  }
}

// Usage
const user = User.fromPrimitives({
  id: "u-001",
  email: "ada@example.com",
  name: "Ada Lovelace",
})

console.log(user.getId())        // "u-001"
console.log(user.toPrimitives()) // { id: "u-001", email: "ada@example.com", name: "Ada Lovelace" }

InvalidArgumentError

InvalidArgumentError extends the native Error class and is the single error type thrown by the library’s validation logic. Catching it at an application boundary (e.g., an HTTP handler or a command handler) lets you return a precise 400 Bad Request response without leaking internal stack traces.

Where it is thrown

LocationTrigger
ValueObject constructorvalue is undefined
StringValueObject constructorvalue is an empty string ("")
EnumValueObject.checkValueIsValidvalue is not in validValues (via throwErrorForInvalidValue)
FilterOperator.fromValue()Unknown filter operator string
OrderType.fromValue()Unknown order direction string

Example — HTTP boundary handler

import { InvalidArgumentError } from "@abejarano/ts-mongodb-criteria"
import type { Request, Response } from "express"
import { UserSearcher } from "./UserSearcher"

export async function searchUsersHandler(req: Request, res: Response) {
  try {
    const searcher = new UserSearcher(/* repository */)
    const results = await searcher.run(req.query)
    res.status(200).json(results)
  } catch (err) {
    if (err instanceof InvalidArgumentError) {
      // Domain validation failed — the request contained bad input
      res.status(400).json({ error: err.message })
    } else {
      // Unexpected infrastructure or programming error
      console.error(err)
      res.status(500).json({ error: "Internal server error" })
    }
  }
}
Because InvalidArgumentError extends Error, it carries .message and .stack just like any standard JavaScript error, making it straightforward to integrate with existing logging and monitoring pipelines.

Build docs developers (and LLMs) love