Por qué cachear envíos de modal
Cuando un envío de modal falla validación, Discord cierra el formulario y tu handler envía una respuesta de error. Si la persona quiere corregir su entrada e intentarlo de nuevo, debe abrir el modal desde cero. Sin caché, todos los valores que escribió antes se pierden, obligándola a completar todo el formulario una segunda vez.
El decorator de clase @ModalCache resuelve esto persistiendo en memoria todos los valores de campo enviados, asociados al ID de la persona usuaria y al customId del modal. Cuando la misma persona abre el modal de nuevo mediante ModalService.buildFor, los valores en caché se precargan en cada campo. Solo necesita corregir el campo que falló validación.
Las entradas de caché se limpian automáticamente tras un envío exitoso, así que no hay limpieza manual en el camino feliz. La opción ttl garantiza que las entradas obsoletas se descarten si la persona nunca vuelve para completar el formulario.
Habilitar caché con @ModalCache
Aplica @ModalCache a nivel de clase sobre cualquier @ModalComponent. El decorator acepta un objeto con propiedad obligatoria ttl medida en segundos. El TTL empieza a contar desde el momento del envío fallido. Formularios cortos como confirmaciones funcionan bien con TTL de 60 a 120 segundos, mientras que aplicaciones de varios campos pueden usar 300 a 600 segundos.
import {
ModalComponent,
ModalInput,
ModalValidate,
ModalCache,
minWords,
emailFormat,
} from '@spraxium/components';
@ModalComponent({ id: 'apply-modal', title: 'Staff Application' })
@ModalCache({ ttl: 300 })
export class ApplicationModal {
@ModalInput({
label: 'Email Address',
style: 'short',
required: true,
})
@ModalValidate([emailFormat()])
email!: string;
@ModalInput({
label: 'Why do you want to join?',
style: 'paragraph',
required: true,
minLength: 50,
})
@ModalValidate([minWords(20)])
motivation!: string;
@ModalInput({
label: 'Relevant Experience',
style: 'paragraph',
required: true,
})
@ModalValidate([minWords(10)])
experience!: string;
}Mostrar un modal con valores en caché
Para cargar valores en caché al reabrir el modal, usa ModalService.buildFor en lugar de build. Este método acepta la interacción que lo dispara como segundo argumento. Extrae el ID de usuario desde la interacción, comprueba la caché para una entrada coincidente y precarga cada valor de campo si existe. Si no hay entrada en caché, el modal se abre con sus valores por defecto.
import { ButtonHandler, Ctx } from '@spraxium/components';
import { ModalService } from '@spraxium/components';
import type { ButtonInteraction } from 'discord.js';
import { OpenApplyButton } from './open-apply.button';
import { ApplicationModal } from './application.modal';
@ButtonHandler(OpenApplyButton)
export class OpenApplyHandler {
constructor(private readonly modals: ModalService) {}
async handle(@Ctx() interaction: ButtonInteraction): Promise<void> {
const modal = this.modals.buildFor(ApplicationModal, interaction);
await interaction.showModal(modal);
}
}Formato de clave de caché y TTL
La clave de caché es el string customId:userId, construida desde el valor id del componente modal y el snowflake ID de la persona usuaria en Discord. Eso significa que cada persona tiene su propia entrada de caché independiente por modal. Dos personas distintas completando el mismo formulario no interfieren entre sí.
El TTL se mide desde el momento en que se escribe la entrada de caché por fallo de validación. Si la persona no vuelve a abrir el modal dentro de la ventana de TTL, la entrada expira y la siguiente apertura muestra un formulario vacío. No existe ventana deslizante: el TTL no se reinicia en cada reapertura. Para formularios largos donde la persona puede tardar, usa TTL de 600 segundos o más.
Una vez que la persona envía correctamente, el dispatcher llama automáticamente a ModalService.clearCacheFor. Esto significa que la caché se autogestiona tanto en el camino de éxito como en el de expiración, sin limpieza manual en tu código de handler.
Referencia de API de caché
ModalService expone tres métodos para gestionar caché de forma directa. Se usan internamente por el dispatcher y el flujo buildFor, pero son públicos por si necesitas limpiar o inspeccionar entradas desde tu propio código de handler.
| Método | Firma | Propósito |
|---|---|---|
| storeCache | storeCache(modal, interaction, values) | Escribe valores de campo en caché para la combinación de modal y usuario dada. |
| getCacheFor | getCacheFor(modal, interaction) | Lee valores de campo en caché. Devuelve null si no hay entrada o el TTL expiró. |
| clearCacheFor | clearCacheFor(modal, interaction) | Elimina la entrada de caché para el modal y usuario dados. Se llama automáticamente tras un envío exitoso. |
Combinar caché con modales dinámicos
@ModalCache se compone de forma natural con @ModalDynamic y @ModalWhen. Puedes agregar caché a cualquier modal dinámico sin configuración adicional. La caché almacena valores de campo como un mapa plano clave-valor, sin importar cómo se hayan generado, por lo que incluso campos incluidos condicionalmente al enviar quedan cacheados. En la siguiente apertura, buildFor restaura valores cacheados para los campos que estén presentes en el nuevo esquema resuelto.
import { Ctx, SlashCommandHandler } from '@spraxium/common';
import { ModalService } from '@spraxium/components';
import type { ChatInputCommandInteraction } from 'discord.js';
import { ConfigModal } from '../components/config-modal.modal';
import { ConfigureCommand } from './configure.command';
@SlashCommandHandler(ConfigureCommand)
export class ConfigureCommandHandler {
constructor(private readonly modals: ModalService) {}
async handle(@Ctx() interaction: ChatInputCommandInteraction): Promise<void> {
const featureFlags = await this.getFlags(interaction.guildId);
const modal = this.modals.buildFor(ConfigModal, interaction);
await interaction.showModal(modal);
}
private async getFlags(guildId: string): Promise<string[]> {
return ['moderation', 'logging'];
}
}