The bot uses Sequelize for persistence. The default driver is SQLite (sqlite3 package), but
any Sequelize-supported database works. Each module declares its own models in models-dir (typically models/).
A model file exports a class extending Model with a static init(sequelize) method:
// modules/welcomer/models/User.js
const {DataTypes, Model} = require('sequelize');
module.exports = class WelcomerUser extends Model {
static init(sequelize) {
return super.init({
id: {
autoIncrement: true,
type: DataTypes.INTEGER,
primaryKey: true
},
userID: DataTypes.STRING,
channelID: DataTypes.STRING,
messageID: DataTypes.STRING,
timestamp: DataTypes.DATE
}, {
tableName: 'welcomer_User',
timestamps: true,
sequelize
});
}
};The loader calls init(sequelize) for you and registers the model under client.models[<moduleName>][<filename>]. The
filename without .js becomes the key - User.js → client.models['welcomer']['User'].
tableName: prefix with the module name, e.g.welcomer_User, to avoid collisions across modules.timestamps: trueaddscreatedAtandupdatedAtautomatically. Skip if you don't need them.- Primary key: an auto-incrementing
idis the simplest choice. Use a composite key only when you need it. - Class name: doesn't have to match the filename, but matching keeps stack traces readable. Prefix with the module
if you have multiple modules with similarly-named models (e.g.
WelcomerUsernot justUser).
Models are available on client.models after the bot starts:
// modules/welcomer/events/guildMemberAdd.js
module.exports.run = async (client, member) => {
const User = client.models['welcomer']['User'];
await User.create({
userID: member.id,
channelID: '...',
messageID: '...',
timestamp: new Date()
});
};All standard Sequelize methods are available: findOne, findAll, findOrCreate, update, destroy, count,
bulkCreate, etc.
The bot calls sequelize.sync() at startup, which creates missing tables. It does not add columns to existing
tables, nor modify or remove existing columns. So whenever you add, rename, change, or drop a field on an existing
model, ship a migration alongside it so existing installs pick up the schema change.
Migrations are file-based and run automatically on boot by an Umzug-based runner -
you drop a file into your module's migrations/ directory and the runner discovers it, applies it once, tracks it, and
backs up the affected tables first. See migration.md for the full guide.
Define associations from the module's botReady handler, after every model has been initialized:
// modules/example/events/botReady.js
module.exports.run = (client) => {
const A = client.models['example']['A'];
const B = client.models['example']['B'];
A.hasMany(B, {foreignKey: 'aId'});
B.belongsTo(A, {foreignKey: 'aId'});
};
module.exports.ignoreBotReadyCheck = true;- Use
attributes: ['col1', 'col2']to limit returned columns on hot paths. - Index columns you query on with
indexes: [{fields: ['userID']}]in the second argument ofsuper.init. - Batch inserts with
bulkCreateinstead of loopingcreate. - For SQLite, write-heavy workloads benefit from
sequelize.transaction()around batches.