> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modelence.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stores

> MongoDB collections with TypeScript schemas and helper methods

Stores in Modelence provide a type-safe interface for MongoDB collections with built-in schema validation, custom methods, and indexing support.

## Overview

A Store represents a MongoDB collection with:

* **Type-safe schemas** using Modelence schema types (based on Zod)
* **Custom document methods** for business logic
* **MongoDB indexes** for query performance
* **Search indexes** for MongoDB Atlas Search
* **CRUD operations** with full TypeScript support

## Creating a Store

Define a Store by specifying a collection name and configuration:

```typescript theme={null}
import { Store, schema } from 'modelence/server';

export const dbTodos = new Store('todos', {
  schema: {
    title: schema.string(),
    isCompleted: schema.boolean(),
    dueDate: schema.date().optional(),
    userId: schema.userId(),
    createdAt: schema.date(),
  },

  indexes: [
    { key: { userId: 1 } },
    { key: { dueDate: 1 } },
  ],

  methods: {
    isOverdue() {
      return this.dueDate ? this.dueDate < new Date() : false;
    }
  }
});
```

## Schema Definition

Modelence schemas are based on and closely resemble Zod types. Available schema types include:

```typescript theme={null}
{
  // Primitive types
  name: schema.string(),
  age: schema.number(),
  isActive: schema.boolean(),
  createdAt: schema.date(),

  // Optional fields
  description: schema.string().optional(),

  // Arrays
  tags: schema.array(schema.string()),

  // Objects
  metadata: schema.object({
    key: schema.string(),
    value: schema.string(),
  }),

  // Built-in Modelence types
  userId: schema.userId(),  // References a user ID
}
```

## Custom Methods

Add custom methods to documents for business logic:

```typescript theme={null}
export const dbProducts = new Store('products', {
  schema: {
    name: schema.string(),
    price: schema.number(),
    discount: schema.number().optional(),
  },

  methods: {
    getFinalPrice() {
      return this.discount
        ? this.price * (1 - this.discount / 100)
        : this.price;
    },

    hasDiscount() {
      return !!this.discount && this.discount > 0;
    }
  }
});

// Usage
const product = await dbProducts.findById(productId);
console.log(product.getFinalPrice());  // Custom method available
```

## Indexes

For index configuration and examples, see [Indexes](/indexes). The indexes
guide covers:

* MongoDB `indexes`
* Atlas `searchIndexes`
* `indexCreationMode` (`blocking` vs `background`) and startup behavior
  with migrations

## CRUD Operations

Stores provide comprehensive methods for data operations:

### Finding Documents

```typescript theme={null}
// Find one document
const todo = await dbTodos.findOne({ userId: user.id });

// Find by ID
const todo = await dbTodos.findById(todoId);

// Require one (throws error if not found)
const todo = await dbTodos.requireById(todoId);

// Fetch multiple documents
const todos = await dbTodos.fetch(
  { userId: user.id },
  {
    projection: { title: 1, isCompleted: 1, createdAt: 1 },
    sort: { createdAt: -1 },
    limit: 10
  }
);

// Exclude heavy fields when they are not needed
const chunks = await dbDocumentChunks.fetch(
  { documentId },
  { projection: { embedding: 0 } }
);

// Count documents
const count = await dbTodos.countDocuments({ isCompleted: false });
```

### Inserting Documents

```typescript theme={null}
// Insert one
const { insertedId } = await dbTodos.insertOne({
  title: 'Buy groceries',
  isCompleted: false,
  userId: user.id,
  createdAt: new Date()
});

// Insert many
const result = await dbTodos.insertMany([
  { title: 'Task 1', isCompleted: false, userId: user.id, createdAt: new Date() },
  { title: 'Task 2', isCompleted: false, userId: user.id, createdAt: new Date() }
]);
```

### Updating Documents

```typescript theme={null}
// Update one
await dbTodos.updateOne(
  { id: todoId },
  { $set: { isCompleted: true } }
);

// Update one with convenience selector (by ID string)
await dbTodos.updateOne(
  todoId,
  { $set: { isCompleted: true } }
);

// Upsert (update or insert)
await dbTodos.upsertOne(
  { userId: user.id, title: 'Unique task' },
  { $set: { isCompleted: false } }
);

// Update many
await dbTodos.updateMany(
  { userId: user.id },
  { $set: { isArchived: true } }
);
```

### Deleting Documents

```typescript theme={null}
// Delete one
await dbTodos.deleteOne({ id: todoId });

// Delete many
await dbTodos.deleteMany({ isCompleted: true });
```

### Advanced Operations

```typescript theme={null}
// Aggregation pipeline
const stats = await dbTodos.aggregate([
  { $match: { userId: user.id } },
  { $group: {
    _id: '$isCompleted',
    count: { $sum: 1 }
  }}
]).toArray();

// Bulk write operations
await dbTodos.bulkWrite([
  { insertOne: { document: { title: 'New task', /* ... */ } } },
  { updateOne: { filter: { id: todoId }, update: { $set: { isCompleted: true } } } },
  { deleteOne: { filter: { id: oldTodoId } } }
]);
```

## Including Stores in Modules

Register stores in your module to automatically provision them:

```typescript theme={null}
import { Module } from 'modelence/server';
import { dbTodos } from './db';

export default new Module('todo', {
  stores: [dbTodos],

  queries: {
    async getAll() {
      return await dbTodos.fetch({});
    }
  }
});
```

When your application starts, Modelence will:

* Provision the collection in MongoDB
* Create all configured indexes
* Create all configured search indexes

<Tip>
  Stores handle all MongoDB connection management automatically. Just define your Store and include it in a module - Modelence takes care of the rest.
</Tip>

## Extending Stores

Use the `extend()` method to add custom schema fields, indexes, methods, and search indexes to any store, including system collections:

```typescript theme={null}
import { schema, dbUsers } from 'modelence/server';

// Extend the users collection
export const extendedDbUsers = dbUsers.extend({
  schema: {
    firstName: schema.string(),
    lastName: schema.string(),
    companyId: schema.objectId().optional(),
  },
  indexes: [
    { key: { companyId: 1 } },
    { key: { lastName: 1, firstName: 1 } },
  ],
  methods: {
    getFullName() {
      return `${this.firstName} ${this.lastName}`;
    }
  }
});

// Fully typed with new fields and methods!
const user = await extendedDbUsers.findOne({ firstName: 'John' });
console.log(user?.getFullName()); // ✅ Custom methods work
console.log(user?.companyId);     // ✅ Type-safe fields
console.log(user?.handle);        // ✅ Original fields preserved
```

<Note>
  The `extend()` method creates a new Store instance with merged schema, methods, indexes, and search indexes. The extended store shares the same MongoDB collection as the original.
</Note>

## Best Practices

1. **Define stores per domain** - Keep related data in the same module
2. **Plan index strategy** - See [Indexes](/indexes) for configuration patterns and startup mode tradeoffs
3. **Leverage custom methods** - Encapsulate business logic in document methods
4. **Type safety** - Let TypeScript guide you with schema-based types
5. **Use Atlas Search intentionally** - Use `searchIndexes` for advanced full-text search use cases
6. **Extend system collections early** - Extend system collections like `dbUsers` before using them in your application
7. **Use sparse indexes** - For optional fields with low cardinality, use `sparse: true` to save space

## API Reference

For a complete list of available methods and detailed API documentation, see the [Store API Reference](/api-reference/modelence/server/classes/Store).
