2021.10 v1.14

It has been a busy month for Leoric, for there are more than one page of pull requests, which includes some major changes:

TypeScript Declarations

Leoric includes a declaration file since v0.x, which is very crude and never tested in actual TypeScript projects. In October we’ve been migrating an internal web application to TypeScript by generating declaration files for the model layer, which in turn made us recognize that there were quite a few mistakes in the d.ts.

Let’s take a look of the coding experience now with the d.ts fixed:


const result = await User.average('age').average('height').group('sex');
// The actual type should be ResultSet<{ age, height, sex }>
// TypeScript currently can only deduce ResultSet<Record<string, Literal>> | number


Usually we can update existing model instances with model.update(values). In Sequelize mode, this method is expected to work a bit differently than the default implementation, such as:

Type Casting on Query Values

When querying with models, the passed query values might not have the same type as expected, such as:

User.findOne(ctx.query.id);  // '1'
User.findOne({ name: 9527 });

which is equivalent of SQLs like below:

SELECT * FROM users WHERE id = '1';
SELECT * FROM users WHERE name = 9527;

MySQL will have these values casted to expected types when performing the query, hence records like id=1 or name='9527' could be matched. But these kinds of queries have performance issues, in some cases they won’t be mapped to correct index.

Hence in October, we’ve refactored the type casting of attributes to make following queries work better:

User.findOne('name = ?', name);
User.findOne({ name });
User.findOne({ name: { $ne: name });

Date is handled in a bit different way. If some column have specific data types, such as omitting milliseconds or so, then following queries will change accordingly:

// birthday DATEONLY -- keeps only YYYY-MM-DD
User.findOne({ birthday: new Date() });
// SELECT * FROM users WHERE birthday = '2021-11-10';

// created_at DATETIME(0) -- drops milliseconds because format is YYYY-MM-DD hh:mm:dd
User.findOne({ createdAt: new Date() });
// SELECT * FROM users WHERE created_at = '2021-11-10 07:35:00.000';

Result Dispatching

The return type of Model.where() or the Model.findAll() in Sequelize adapter might be one of the following:

If the query result includes only one aggregated value, the value might be returned directly instead, such as:

const count = await User.count();
const age = await User.average('age');

The complete result dispatching logic is like below:

Following example consists of all the conditions listed above

const users = await User.findAll();
// Collection<User>

const averageHeights = await User.average('height').group('sex');
// ResultSet<{ sex, height }>

const generations = await User.select('DISTINCT YEAR(birthday) AS year');
// ResultSet<{ year }>

The result dispatching strategy is discussed with more detail in 查询结果分发策略


Please visit our Releases page to get the whole list of changelog, or check the commits between v1.12.0…v1.14.2 manually at https://github.com/cyjake/leoric/compare/v1.12.0…v1.14.2

v2.x Milestone

v2.x is scheduled to be released in 2022.1.1, which is planned to introduce several breaking changes, such as taming the dangerous realm.sync().