api-database-sequelize-e59594 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited api-database-sequelize-e59594 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: Sequelize is a promise-based ORM for PostgreSQL, MySQL, MariaDB, SQLite, and MS SQL Server. Use class-based models withModel.init()(v6) or decorators (v7) for type-safe definitions. Always useInferAttributes/InferCreationAttributeswithdeclarefor TypeScript models. Useincludefor eager loading to avoid N+1. Prefer managed transactions (auto-commit/rollback). Association alias (as) must match between definition andinclude. Paranoid mode requirestimestamps: true. v7 is alpha --- most production code uses v6.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)(You MUST use `declare` on all model class properties to prevent TypeScript from emitting class fields that conflict with Sequelize's internal attribute storage)
(You MUST pass `{ transaction: t }` to every query inside a transaction callback --- missing this causes operations to run outside the transaction and skip rollback)
(You MUST use `include` for eager loading related models --- fetching associations in loops creates N+1 query problems)
(You MUST match the `as` alias in `include` with the alias used in the association definition --- mismatches silently return `null` for the association)
</critical_requirements>
Auto-detection: sequelize, Sequelize, Model.init, DataTypes, InferAttributes, InferCreationAttributes, CreationOptional, belongsTo, hasMany, hasOne, belongsToMany, findAll, findByPk, Op.and, Op.or, sequelize-cli, queryInterface, paranoid
When to use:
When NOT to use:
Key patterns covered:
Detailed Resources:
<philosophy>
Sequelize is a traditional, feature-rich ORM that maps JavaScript classes to database tables. Unlike schema-first ORMs, you define models in code and optionally generate migrations from them.
Core principles:
v6 vs v7:
Model.init() for model definitions.@Attribute, @PrimaryKey), scoped packages (@sequelize/core), and CLS is enabled by default via AsyncLocalStorage. The CLI is not yet ready for v7.</philosophy>
<patterns>
Configure the connection with dialect, pool, and logging options.
import { Sequelize } from "sequelize";
const MIN_POOL_SIZE = 0;
const MAX_POOL_SIZE = 10;
const POOL_ACQUIRE_TIMEOUT_MS = 30000;
const POOL_IDLE_TIMEOUT_MS = 10000;
export const sequelize = new Sequelize({
dialect: "postgres",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
logging: process.env.NODE_ENV === "development" ? console.log : false,
pool: {
min: MIN_POOL_SIZE,
max: MAX_POOL_SIZE,
acquire: POOL_ACQUIRE_TIMEOUT_MS,
idle: POOL_IDLE_TIMEOUT_MS,
},
});Why good: Named constants for pool config, conditional logging, explicit pool sizing
// BAD: Connection string with no pool config
const sequelize = new Sequelize("postgres://user:pass@localhost:5432/db");Why bad: Default pool settings may exhaust connections under load, no logging control
See examples/core.md for connection URI patterns and graceful shutdown.
Use InferAttributes, InferCreationAttributes, and declare for type-safe models.
import {
Model,
DataTypes,
type InferAttributes,
type InferCreationAttributes,
type CreationOptional,
} from "sequelize";
import { sequelize } from "./connection";
export class User extends Model<
InferAttributes<User>,
InferCreationAttributes<User>
> {
declare id: CreationOptional<number>;
declare email: string;
declare name: string | null;
declare role: CreationOptional<string>;
declare createdAt: CreationOptional<Date>;
declare updatedAt: CreationOptional<Date>;
}
User.init(
{
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
email: { type: DataTypes.STRING, allowNull: false, unique: true },
name: { type: DataTypes.STRING, allowNull: true },
role: { type: DataTypes.STRING, allowNull: false, defaultValue: "user" },
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE,
},
{ sequelize, tableName: "users" },
);Why good: declare prevents TS from emitting class fields, CreationOptional marks auto-generated fields, explicit tableName avoids pluralization surprises
// BAD: Missing declare keyword
export class User extends Model {
id!: number; // Emitted as class field, conflicts with Sequelize internals
email!: string;
}Why bad: Without declare, TypeScript emits class fields that override Sequelize's internal getters/setters, causing silent data loss
See examples/core.md for association mixin typing and NonAttribute usage.
Define relationships between models. The as alias is critical for eager loading.
// One-to-Many: User has many Posts
User.hasMany(Post, { foreignKey: "authorId", as: "posts" });
Post.belongsTo(User, { foreignKey: "authorId", as: "author" });
// Many-to-Many: Post has many Tags through PostTag
Post.belongsToMany(Tag, { through: PostTag, foreignKey: "postId", as: "tags" });
Tag.belongsToMany(Post, { through: PostTag, foreignKey: "tagId", as: "posts" });Why good: Explicit foreignKey prevents naming ambiguity, as enables clean eager loading
// BAD: No alias, then trying to include with one
User.hasMany(Post, { foreignKey: "authorId" });
// Later:
User.findAll({ include: { model: Post, as: "posts" } }); // Error or null!Why bad: If you define the association without as, you cannot use as in include --- Sequelize won't find the association. The alias must match exactly between definition and query.
See examples/associations.md for all association types, eager loading, and the include alias contract.
Fetch related models in a single query to avoid N+1.
const DEFAULT_PAGE_SIZE = 20;
// Include with alias (must match association definition)
const users = await User.findAll({
include: [{ model: Post, as: "posts" }],
limit: DEFAULT_PAGE_SIZE,
});
// Nested includes
const posts = await Post.findAll({
include: [
{
model: User,
as: "author",
include: [{ model: Profile, as: "profile" }],
},
{ model: Tag, as: "tags" },
],
});Why good: Single query with JOINs, nested includes for deep relations, alias matches definition
// BAD: N+1 query pattern
const users = await User.findAll();
for (const user of users) {
const posts = await Post.findAll({ where: { authorId: user.id } }); // N queries!
}Why bad: 1 query for users + N queries for posts, performance degrades linearly with record count
See examples/associations.md for required includes (INNER JOIN), separate queries, and filtering included models.
Prefer managed transactions --- Sequelize auto-commits on success and auto-rolls back on thrown errors.
const result = await sequelize.transaction(async (t) => {
const user = await User.create(
{ email: "[email protected]", name: "Alice" },
{ transaction: t },
);
await Profile.create(
{ userId: user.id, bio: "Developer" },
{ transaction: t },
);
return user;
});
// result is the return value of the callbackWhy good: Auto-commit/rollback, clean error propagation, return value passed through
// BAD: Forgetting to pass transaction
await sequelize.transaction(async (t) => {
const user = await User.create({ email: "[email protected]" }); // Missing { transaction: t }!
await Profile.create({ userId: user.id }, { transaction: t });
});Why bad: User.create runs outside the transaction --- if Profile.create fails and rolls back, the user record persists, leaving inconsistent data
See examples/transactions.md for unmanaged transactions, CLS auto-pass, and isolation levels.
Paranoid mode sets deletedAt instead of deleting the row. Requires timestamps: true.
export class Post extends Model<
InferAttributes<Post>,
InferCreationAttributes<Post>
> {
declare id: CreationOptional<number>;
declare title: string;
declare deletedAt: CreationOptional<Date | null>;
// ...
}
Post.init(
{
id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
title: { type: DataTypes.STRING, allowNull: false },
},
{ sequelize, tableName: "posts", paranoid: true },
);
// Soft delete --- sets deletedAt
await post.destroy();
// Hard delete --- actually removes the row
await post.destroy({ force: true });
// Restore soft-deleted record
await post.restore();
// Include soft-deleted records in queries
const allPosts = await Post.findAll({ paranoid: false });Why good: paranoid: true enables soft deletes, force: true for hard delete escape hatch, paranoid: false in queries to include deleted records, restore() to undo
See examples/advanced.md for paranoid mode with eager loading gotchas.
</patterns>
<red_flags>
High Priority Issues:
declare --- TypeScript emits class fields that override Sequelize getters/setters, causing silent data corruption{ transaction: t } on queries inside transaction callbacks --- operations run outside the transaction and skip rollbackinclude to eager load associations in a single queryas alias between association definition and include --- silently returns null for the associationMedium Priority Issues:
paranoid: true with timestamps: false --- paranoid mode silently does nothing without timestampsas then using as in include --- Sequelize cannot find the associationhasMany/belongsTo gets accessor methodsforeignKey on associations --- Sequelize auto-generates names that may not match your database columnsfindAll without limit in production --- unbounded queries can crash the serverGotchas & Edge Cases:
bulkCreate/update/destroy (static) do NOT fire individual hooks (beforeCreate, afterUpdate) by default --- pass { individualHooks: true } to enable (performance cost: loads all instances into memory)defaultScope is applied to ALL queries including findByPk --- use .unscoped() when you need unfiltered accesswhere on the same field overwrite (not AND) by default --- enable whereMergeStrategy: 'and' for combiningrequired: true on include converts LEFT JOIN to INNER JOIN --- parent records without the association are excludedsave() on a parent does NOT cascade to eager-loaded children --- save each child individuallybelongsToMany through junction table data is accessible via record.JunctionModel but easy to missOp.not in v6 sometimes produces unexpected SQL depending on dialect --- test complex operator combinationsUser -> Users) --- always set explicit tableNameBIGINT and DECIMAL return strings in JavaScript, not numbers --- parse them at your boundaryafterCommit hook only fires on successful commit, not on rollback --- don't use it for cleanup that must always runfindOrCreate can fail with race conditions if no unique constraint exists on the where fieldupsert returns [instance, created] but created is unreliable on some dialects (MySQL/SQLite may always return true or null)findAll with where on included paranoid models may unexpectedly return soft-deleted itemssequelize.close() on shutdown leaks connections from the pool</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST use `declare` on all model class properties to prevent TypeScript from emitting class fields that conflict with Sequelize's internal attribute storage)
(You MUST pass `{ transaction: t }` to every query inside a transaction callback --- missing this causes operations to run outside the transaction and skip rollback)
(You MUST use `include` for eager loading related models --- fetching associations in loops creates N+1 query problems)
(You MUST match the `as` alias in `include` with the alias used in the association definition --- mismatches silently return `null` for the association)
Failure to follow these rules will cause silent data corruption, broken transactions, N+1 performance degradation, and missing association data.
</critical_reminders>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.