Big list of potentials issues I want to tackle at some point but I haven't opened a proper issue or PR yet to avoid spamming.
I also have a backlog now: https://github.com/orgs/sequelize/projects/1
- sequelize/sequelize#13773 -
Sequelize.attr
(alternative toSequelize.col
) - https://github.com/sequelize/meetings/issues/12 - Improving auto Timestamp fields
- https://github.com/sequelize/meetings/issues/14 - Make Associations TypeScript friendly
- https://github.com/sequelize/meetings/issues/15 - Decorator-based model definition
- https://github.com/sequelize/meetings/issues/16 - Improve support for Public Class Fields
- sequelize/sequelize#14259 - Deprecate
DataTypes.STRING
, addDataTypes.{BINARY,VARCHAR,VARCHAR.BINARY,VARCHAR.N,CHAR.N}
- sequelize/sequelize#14295 - Replace
DataTypes.DATE
withDataTypes.DATETIME.ZONED
- sequelize/sequelize#14297 - pre-fill bind parameters in
literal()
Allow arrays as the value to the 'unique' option of a field:
User.init({
source: { unique: ['source-slug', 'source-id'] },
sourceSlug: { unique: 'source-slug' },
sourceId: { unique: 'source-id' },
});
Automatically convert the 'unique' option from attributes into indexes, so indexes is the source of truth
In v6, deferrable
cannot be used in belongsTo
. It must be used in define
instead. We should fix this.
const A = sequelize.define('A', {
BId: {
type: DataTypes.INTEGER,
references: {
deferrable: Deferrable.INITIALLY_IMMEDIATE() // notice the (), due to wrong typings
}
}
});
const B = sequelize.define('B');
A.belongsTo(B);
Should be this instead:
const A = sequelize.define('A');
const B = sequelize.define('B');
A.belongsTo(B, {
foreignKey: { deferrable: Deferrable.INITIALLY_IMMEDIATE },
});
There should be a way to defer constraint checks for part of a transaction. For instance:
sequelize.transaction(async transaction => {
// ... do actions with constraints checked immediately.
await transaction.withDeferredConstraints(() => {
// ... do actions with constraints deferred.
});
// ... do actions with constraints checked immediately.
})
Or
sequelize.transaction(async transaction => {
// ... do actions with constraints checked immediately.
await transaction.deferConstraints();
// ... do actions with constraints deferred.
await transaction.undeferConstraints();
// ... do actions with constraints checked immediately.
})
The way Attributes are defined in Sequelize does not play nice with inheritance.
Say you have the inheritance tree Model
-> BaseModel
-> User
, and you have a method that accepts model: ModelStatic<Model>
as a parameter. Checking whether Model
is a subclass of BaseModel
will not let you access the attributes as they could be completely different from a parent model to a child one. i.e. the following does not work:
if (isSubclassOfBaseModel(model)) {
await model.findAll({
where: {
// TypeScript thinks this does not exist.
baseModelAttribute: 1,
},
});
}
In Sequelize ?
is a positional replacement token, and :name
is a named replacement token.
This is confusing because most dialects use ?
for bind parameters.
Sequelize use postgres-style bind parameters: $1
and $name
.
We should stop using ?
as the positional replacement token, and instead use :1
, to be symmetrical with our bind parameters and remove the confusion.
Prepared statements are a good way to optimize both the JavaScript and SQL:
- The SQL query is only built once, instead of going through the QueryBuilder every time
- The SQL is only parsed once by the database
// executed only once:
const getUserByName = User.select()
.where({ name: bind('name') })
.toPreparedStatement('find-user-by-name');
// or
const getUserByName = User.prepareFindAll('find-user-by-name', {
where: { name: bind('name') },
});
// later:
const user = await getUserByName({ name: 'Zoe' });
Advantages:
- Clear that prepared statements are created once, and that their contents should not be modified between two calls (outside of passing different bind parameters).
- Cleanly incorporates into the query builder API
Disadvantages:
- Separates the creation of the query from its usage.
const user = await User.select()
.where({ name: bind('name') })
.findAll({ preparedStatement: 'find-user-by-name' });
const user = await User.findAll({
where: { name: bind('name') },
preparedStatement: 'find-user-by-name',
});
Advantages:
- The creation of the query is located at the same place it is used
- No need for extra methods on Model
Disadvantages:
- We're creating a lot of throwaway objects that will be discarded immediatly
- Users may think they can put dynamic values outside of bind parameters, so we'll need to compare
bind('name')
would be equivalent to doing literal('$name')
: it creates a named bind parameter. Especially useful in prepared statements.
DataType.INTEGER
should throw iftypeof value === 'number' && !Number.isSafeInteger(value)
count
& other aggregate functions returnnumber
. what if the value is not a representable number? Should have an option to return bigint. Should throw ifnumber
is not able to represent the value returned by the dialect.
utils/dialect#diaelcts
should include 'snowflake' as it supports millisecondslib/utils/sequelize-methods
:col
has a weird behavior when you give more than one parameterModel.count({ group: ['count'] })
will return a broken result (becausecount
is both the name of the counted column & the name of the aggregate column). Likely an issue in other aggregate functions too.count
could returnArray<{ group: object, count: number }>
instead ofArray<{ ...group, count: number }>
Main article: https://gist.github.com/ephys/078ee6a2eb31dc209f0de6cea95316e7
- Update docs on RANGE, it is lackluster
- Add support for
reviver
argument inDataTypes.JSON(B)
- Accept TypeScript ENUM in DataTypes.ENUM?
- Add an "after close" hook to Transaction, that is called after either commit or rollback
- Add an "after rollback" hook to Transaction
- Allow choosing between Validators.js & Joi for validation (
@sequelize/validator.js
&@sequelize/joi
?) - Drop support for
ContinuationLocalStorage
, migrate to built-inAsyncLocalStorage
. Model.bulkSave
for saving many models at once- A 'dialect-aware
fn
': It receives the dialect and adapts its code based on it. Mostly for internal use, to provide things likeSequelize.now
- a dialect-specific 'now' sql function.- Must be usable in userland sequelize/sequelize#9142
Model.findInBatch
which returns an async iterator with a simple limit/offset paginationModel.findByCursor
which uses stateless cursor paginationModel.findByCursorInBatch
which returns an async iterator with a stateless cursor pagination
- Allow specifying
logQueryParameters
per query instead of globally. - use
uniqueidentifier
forDataTypes.UUID
in mssql? - Specifying
limit
on an include quietly causes theseparate
option to be turned on. We should throw instead! - Support all Iterables in
{ [Op.isIn]: new Set() }
? - Can we cast values (not columns)? e.g.
cast(myUserProvidedValue, 'JSONB')
- Shorter syntax to cast columns:
where: { 'preferences::jsonb': {} }
AcceptI'm not pursuing this feature anymore as I don't want queries to silently break because a value was accidentally undefined. I'm pursuing work on the Query Builder as an alternative.undefined
inwhere
but ignore the value. So users can easily add/remove SQL conditions based on JS valuesModel.increment
/Model.decrement
return[affectedRows, affectedRowCount]
. We could simplify to just returningaffectedRows
. Need investigation.- Document alias max length for each dialect. If a generated alias is too long: throw with a message recommending to use
minifyAliases
to resolve the issue offset
in query generator is not strict enough. Maybe forbid anything that's not a safe integer, a big int, or a int string?- Deprecate
DataTypes.DATE
, replaced withDataTypes.DATETIME.ZONED
&DataTypes.DATETIME
. sequelize/sequelize#13372 (comment) - Generated columns: sequelize/sequelize#12718
- Readonly columns: sequelize/sequelize#4603
- sequelize/sequelize#11514
- rewrite cli as esm & include cosmiconfig
use nvarchar(max)
+ CHECK(ISJSON(col)=1)
User.findOne({
include: [{
association: User.associations.group,
where: {
// these are not currently type-checked
test: 'test',
}
}],
});
I'm using this task to list the few code cleaning tasks that still need to be done
- Run eslint on JavaScript snippets in markdown files
- Implement review changes from last eslint commit sequelize/sequelize#13930 (review)
- Limit packages in commitlint to each dialects + 'types'
queryInterface
and queryGenerator
should be proper APIs that users can exploit.
This means most of the code in Model methods should be moved to queryInterface
, but be written in a way that specifying models is optional, because it needs to work in transactions too.
This is also true for queryGenerator
. You should be able to pass raw FindOptions
to it, and it normalizes them itself.
Main thread: sequelize/sequelize#2325 (comment)
bindContext should not include any value.
Instead it should be a simple array to know at which position a named bind parameter is. Methods can then use that array to format the bind
option correctly before calling queryRaw
QueryGenerator
methods should always return { query, namedBindPositions }
(instead of { query, bind }
). QueryInterface
can then sort out mapping named bind to positional bind (including for insert, update, etc).
Feedback from AllAwesome497:
function-paren-newline
would look better like this:@typescript-eslint/quotes
would look better withavoidEscape
@typescript-eslint/explicit-member-accessibility
50/50 vote on enforcing 'public' and forbidding 'public' modifier. Either way it should not be mixed. I personnally would forbid it as it doesn't impact the base JS.
Just for better scoping on sub packages, maybe we can use plugin system, for example like this:
plugins
option to use Sequelize plugins, for example validators plugin like@sequelize/joi
.