Skip to content

AI Agents Reference

Skill name
zumito-reference
Description
Critical information for AI agents working with Zumito Framework. Hidden from sidebar.
Category
guides

This page contains information specifically for AI coding agents. It clarifies patterns that are commonly misunderstood and are not explicitly covered in other docs.

Discord imports: always use zumito-framework/discord

Never import from discord.js directly. The framework re-exports everything from discord.js via its own subpath to ensure version consistency.

// CORRECTO
import { Client, GuildMember, TextChannel, EmbedBuilder, PermissionsBitField } from 'zumito-framework/discord';

// INCORRECTO — no hagas esto
import { Client } from 'discord.js';

All types, enums, and classes from discord.js are available through zumito-framework/discord. This includes but is not limited to: Client, Guild, GuildMember, TextChannel, VoiceChannel, Message, EmbedBuilder, ActionRowBuilder, ButtonBuilder, StringSelectMenuBuilder, PermissionsBitField, ChannelType, ApplicationCommandOptionType, Interaction, CommandInteraction, ButtonInteraction, StringSelectMenuInteraction, ModalSubmitInteraction, User, roleMention, userMention, channelMention, inlineCode, bold, italic, hyperlink, etc.

Database access

The framework uses zumito-db, a decorator-based multi-driver ORM. Define models with @Collection and @Field, access them via DatabaseManager/Repository.

Mongoose appears in package.json dependencies for legacy reasons but is not used and will be removed. Do not create Mongoose models or schemas.

How to get the database instance

import { DatabaseManager, ServiceContainer } from 'zumito-framework';

const db = ServiceContainer.get(DatabaseManager) as DatabaseManager;

Auto-loading models from modules

Models placed in a module's models/ folder are automatically imported and registered during module.initialize(). The framework scans for exported classes with @Collection metadata and registers them with DatabaseManager. Schemas are created/ensured immediately after all modules are loaded. No manual registration needed.

Example:

// src/modules/my-module/models/Guild.ts
import { Collection, Field } from 'zumito-framework';

@Collection({ name: 'guilds' })
export class Guild {
    @Field({ type: 'string', primary: true, unique: true })
    guild_id: string;
}

The Guild model is now available via db.getRepository(Guild) anywhere in the app.

Defining models

import { Collection, Field, HasMany } from 'zumito-framework';

@Collection({ name: 'guilds' })
export class Guild {
    @Field({ type: 'string', primary: true, unique: true })
    guild_id: string;

    @Field({ type: 'string', default: 'en' })
    lang: string;
}

Always use explicit type in @Field() options when writing code that might be tested with vitest/esbuild (which does not support emitDecoratorMetadata).

Repository pattern (preferred)

const guilds = db.getRepository(Guild);

// Find
const all = await guilds.find();
const guild = await guilds.findOne({ guild_id: '123' });

// Insert
await guilds.insert({ guild_id: '789', lang: 'es' });

// Update
await guilds.update({ guild_id: '123' }, { lang: 'fr' });

// Delete
await guilds.delete({ guild_id: '789' });

// Count
const total = await guilds.count({ lang: 'en' });

QueryBuilder

const results = await guilds
    .query()
    .where('lang', 'eq', 'es')
    .sort('guild_id', 'asc')
    .limit(10)
    .select(['guild_id', 'prefix'])
    .exec();

Available operators: eq, neq, gt, gte, lt, lte, in, nin, like, between.

Legacy MongoDB access (deprecated)

// @deprecated — use DatabaseManager instead. Only works with mongo driver.
import { Db } from 'mongodb';
const db = ServiceContainer.get(Db);
await db.collection('guilds').findOne({ guild_id: '123' });

The above still works but emits deprecation warnings. Use DatabaseManager + Repository for new code.

Configuration collection convention

Guild settings are stored in a guilds collection. Each document has a guild_id field. Use the GuildDataGetter service to fetch guild settings with defaults merged in:

import { GuildDataGetter, ServiceContainer } from 'zumito-framework';

const guildDataGetter = ServiceContainer.get(GuildDataGetter) as GuildDataGetter;
const settings = await guildDataGetter.getGuildSettings(guildId);

ServiceContainer

The ServiceContainer is a global dependency injection container. Key services are pre-registered by the framework:

Service class How to get Notes
ZumitoFramework ServiceContainer.get(ZumitoFramework) The framework instance
Client (discord) ServiceContainer.get(Client) Discord.js client
DatabaseManager ServiceContainer.get(DatabaseManager) zumito-db ORM instance
TranslationManager ServiceContainer.get(TranslationManager) Translation service
CommandManager ServiceContainer.get(CommandManager) All loaded commands
EventManager ServiceContainer.get(EventManager) Event handling
ErrorHandler ServiceContainer.get(ErrorHandler) Centralized error handling
GuildDataGetter ServiceContainer.get(GuildDataGetter) Guild settings from DB
MemberPermissionChecker ServiceContainer.get(MemberPermissionChecker) Permission checks
TextFormatter ServiceContainer.get(TextFormatter) Text formatting utilities
EmojiFallback ServiceContainer.get(EmojiFallback) Emoji fallback resolution
InviteUrlGenerator ServiceContainer.get(InviteUrlGenerator) Bot invite URL generation
PrefixResolver ServiceContainer.get(PrefixResolver) Guild-specific prefix

When writing a command, the client and framework are available in execute parameters, so you don't need ServiceContainer for those. Use ServiceContainer for other services like GuildDataGetter or ErrorHandler.

import { Command, CommandParameters, ServiceContainer, GuildDataGetter } from 'zumito-framework';
import { Client } from 'zumito-framework/discord';

export class MyCommand extends Command {
    constructor(
        private guildDataGetter: GuildDataGetter = ServiceContainer.get(GuildDataGetter),
    ) {
        super();
    }

    async execute({ message, interaction, client, framework }: CommandParameters): Promise<void> {
        const target = message || interaction;
        // client and framework are already available in params
    }
}

Module structure convention

Each module lives in src/modules/<ModuleName>/ and follows this structure:

src/modules/<ModuleName>/
├── commands/          # Command classes
├── events/            # Event classes (subfolder = source, e.g., events/discord/)
│   └── discord/       # Discord.js events
│   └── framework/     # Framework events (e.g., 'ready')
├── models/            # Database model classes (auto-loaded, use @Collection/@Field)
├── routes/            # Express route handlers
├── translations/      # Translation JSON files
│   ├── en.json
│   └── es.json
├── config/
│   └── default.ts     # Default guild config values
└── index.ts           # Module entry (extends Module)

The module entry extends Module from zumito-framework:

import { Module } from 'zumito-framework';

export class MyModule extends Module {
    async initialize() {
        await super.initialize();  // registers commands, events, translations, routes
    }

    async onAllReady() {
        // Called after all modules initialized, DB ready, slash commands refreshed
    }
}

Translation keys

Translation keys are relative to the current context:

  • In commands: trans("yourKey") resolves to command.<commandName>.yourKey
  • Use $ prefix for absolute keys: trans("$category.otherCommand.someKey")

Translation files are flat JSON objects. The framework merges all module translations.

Slash vs Prefix commands

  • If only execute() is defined: works for both slash and prefix.
  • If executeSlashCommand() is defined: used for slash commands instead of execute().
  • If executePrefixCommand() is defined: used for prefix commands instead of execute().
  • Check which mode by seeing if message or interaction is defined in params.

TypeScript constructor conventions

Use parameter properties (private readonly inline) when constructor parameters are assigned directly to instance properties:

// CORRECTO — parameter properties
export class MyService {
    constructor(
        private readonly framework: ZumitoFramework,
        private readonly someOption: string,
    ) {}
}

// INCORRECTO — declaración + asignación en cuerpo (redundante)
export class MyService {
    private framework: ZumitoFramework;
    private someOption: string;

    constructor(framework: ZumitoFramework, someOption: string) {
        this.framework = framework;
        this.someOption = someOption;
    }
}

When a dependency comes from ServiceContainer.get(), use a default parameter value:

export class MyService {
    constructor(
        private readonly errorHandler: ErrorHandler = ServiceContainer.get(ErrorHandler),
    ) {}
}

Only fall back to the declaration + body pattern when initialization requires logic beyond simple assignment (e.g., calling methods, conditional logic, super() requirements).

File access (FileManager)

The framework provides two file storage modules that implement the same FileManager abstract contract (get, put, delete, list, getUrl, exists). Use the one that matches the deployment environment.

Local filesystem (@zumito-team/local-filesystem)

Stores files on the local disk.

import { LocalFileManager, AssetInfo, FileManager } from '@zumito-team/local-filesystem';
import { ServiceContainer } from 'zumito-framework';

const fm = ServiceContainer.get(LocalFileManager) as LocalFileManager;

// Upload — returns a URL string
const url = await fm.put('images/banner.png', buffer, 'image/png');

// Read
const data: Buffer | null = await fm.get('images/banner.png');

// Delete
await fm.delete('images/banner.png');

// List files under a prefix
const files: AssetInfo[] = await fm.list('images/');

// Check existence
const exists: boolean = await fm.exists('images/banner.png');

// Get public URL
const publicUrl: string = await fm.getUrl('images/banner.png');

Configuration in zumito.config.ts:

import { LocalFilesystemModule } from '@zumito-team/local-filesystem';

export const config = {
    bundles: [
        {
            path: 'node_modules/@zumito-team/local-filesystem',
        }
    ]
};

S3-compatible storage (@zumito-team/s3-assets)

Works with AWS S3, DigitalOcean Spaces, MinIO, Cloudflare R2, etc. Same API as local filesystem.

import { S3FileManager, AssetInfo, FileManager } from '@zumito-team/s3-assets';
import { ServiceContainer } from 'zumito-framework';

const fm = ServiceContainer.get(S3FileManager) as S3FileManager;

// Same API as LocalFileManager — get, put, delete, list, getUrl, exists
const url = await fm.put('uploads/doc.pdf', pdfBuffer, 'application/pdf');
const data = await fm.get('uploads/doc.pdf');

Configuration in zumito.config.ts (DigitalOcean Spaces example):

import { S3AssetsModule } from '@zumito-team/s3-assets';

export const config = {
    bundles: [
        {
            path: 'node_modules/@zumito-team/s3-assets',
            options: {
                bucket: 'my-bucket',
                region: 'nyc3',
                endpoint: 'https://nyc3.digitaloceanspaces.com',
                accessKeyId: 'DO00...',
                secretAccessKey: '...',
                publicUrlBase: 'https://my-bucket.nyc3.cdn.digitaloceanspaces.com',
            }
        }
    ]
};

Writing code against the abstract contract

To write storage-agnostic code, depend on the abstract FileManager from @zumito-team/file-manager:

import { FileManager, type AssetInfo } from '@zumito-team/file-manager';
import { ServiceContainer } from 'zumito-framework';

// Will return whichever implementation is registered (Local or S3)
const fm = ServiceContainer.get(FileManager) as FileManager;
const url = await fm.put('key', buffer, 'text/plain');

Common pitfalls

  1. Don't instantiate the Discord Client yourself — the framework creates and manages it.
  2. Don't create Mongoose models. Use zumito-db decorators (@Collection, @Field) and DatabaseManager/Repository.
  3. Don't import from discord.js directly. Use zumito-framework/discord.
  4. Don't call client.login() — the framework handles authentication via FrameworkSettings.discordClientOptions.token.
  5. In DM channels, guild and guildSettings will be undefined. Always guard against this.
  6. The framework uses ESM ("type": "module" in package.json). Always use .js extensions in relative imports even in TypeScript files.
  7. Always specify type in @Field({ type: 'string' }) options — emitDecoratorMetadata inference is not available in vitest/esbuild test environments.