# Mongoose services

Write a service class for each MongoDB collection with the mongoose plugin, and read those services from any handler.

Without services, every handler that looks up a user imports the model and writes its own `findOne` call. You copy the same query into several handlers, so changing how you look a user up means finding every copy.

A service keeps a collection's queries in one class. Extend [`MongooseService`](https://seedcord.org/docs/packages/plugin-mongoose/latest/classes/mongoose-service), declare the schema as a static, and decorate the class with a key. Handlers then call its methods through that key.

```ts title="src/services/Users.ts"
import {
    MongooseService,
    RegisterMongooseService
} from '@seedcord/plugin-mongoose';
import mongoose from 'mongoose';

import type { MongooseDocument } from '@seedcord/plugin-mongoose';

interface IUser extends MongooseDocument {
    username: string;
    streak: number;
}

@RegisterMongooseService('users')
export class Users extends MongooseService<IUser> {
    public static schema = new mongoose.Schema<IUser>({
        username: { type: String, required: true, unique: true },
        streak: { type: Number, default: 0 }
    });

    public findByName(username: string): Promise<IUser | null> {
        return this.model.findOne({ username }).lean().exec();
    }
}
```

`Users.ts` does several things at once, so let's go through it one part at a time.

* **`IUser`** lists the fields a user document has. It extends [`MongooseDocument`](https://seedcord.org/docs/packages/plugin-mongoose/latest/interfaces/mongoose-document), which declares `_id: string`, the one field every document has.
* **[`@RegisterMongooseService('users')`](https://seedcord.org/docs/packages/plugin-mongoose/latest/functions/register-mongoose-service)** gives the class its key. Your handlers read the service from `services` under that key, typed as `Users` once [codegen has run](#typing-the-registry).
* **`MongooseService<IUser>`** is the base class. Passing `IUser` types `this.model` as `Model<IUser>`, so TypeScript checks the fields in every query on that model against `username` and `streak`. Without the type argument, the model's type only has `_id`.
* **`schema`** is the Mongoose schema the plugin builds the model from. Make sure it's `static`, because the decorator fails to compile without it.
* **`findByName`** keeps the lookup inside the class. Your handlers call that method, so none of them writes the query.

## You never construct a service

The constructor takes the plugin, the core, and a model, so the plugin builds your services itself, right after it connects. It imports every code file under the `dir` you gave the [mongoose plugin](https://next.seedcord.org/guide/plugins/mongoose/), including subfolders, in order of path. That covers `.ts`, `.tsx`, `.mts`, `.js`, `.jsx`, and `.mjs`, and skips type declarations, dotfiles, dot-folders, and `node_modules`. For each exported class that extends `MongooseService` and carries the decorator, it builds the model from `schema` and constructs the class. That constructor call adds the instance to the plugin's [`services`](https://seedcord.org/docs/packages/plugin-mongoose/latest/classes/mongoose#services) map under its key.

> **Warning**
>
> If a class isn't exported or has no `@RegisterMongooseService`, the plugin skips it without throwing or logging anything, and `services.users` reads `undefined`. Codegen skips the class too, so once it has run, `services.users` fails to compile.

## The model name sets the collection

The plugin uses your key as the Mongoose model name. Mongoose pluralizes that name to pick the collection, so a key of `user` reads and writes the `users` collection. That's fine for a new database. If your collection already exists under a different name, pass `modelName` so the model name can differ from the key.

```ts
@RegisterMongooseService('users', { modelName: 'app_user' })
export class Users extends MongooseService<IUser> {
    public static schema = new mongoose.Schema<IUser>({
        username: { type: String, required: true }
    });
}
```

With `modelName: 'app_user'`, mongoose reads the `app_users` collection, while your handlers still use the class as `services.users`. `modelName` takes any non-empty string, so your editor doesn't suggest one and TypeScript can't catch a misspelled model name.

## Typing the registry

The plugin finds your services by importing `dir` when the bot starts, but TypeScript only reads your source. Typing each key by hand means a `declare module` entry per service, which you keep in step with the decorator yourself.

[`seedcord codegen`](https://next.seedcord.org/guide/plugins/typing/) imports `dir` the same way and writes each key it finds into `src/seedcord-gen.ts`. After that, `services.users` is typed as your `Users` service, and the decorator offers the keys codegen found. With a `users` and a `guilds` service, it offers both:

```ts
RegisterMongooseService('
```

Until codegen has run once, the decorator accepts any key, and TypeScript types every entry on `services` as `object`.

> **Warning**
>
> If you add or rename a service, its decorator fails to compile until codegen runs again, since the generated file only lists the keys from the last run. Run `seedcord codegen` after each one.

## Reading a service

A handler gets a service from the plugin's `services` map, under the key you declared.

```ts
const found =
    await this.core.db.services.users.findByName('ada');

await this.reply(
    found ? `Found ${found.username}.` : 'No record.'
);
```

`findByName` resolves to an `IUser` or `null`, so the handler checks `found` before it reads `username`.

If you read `services` before the plugin's `init()` has finished, it throws `PluginServicesNotReady`. Your handlers never read it that early, though, because they only start receiving interactions once `init()` has finished.
