InferAttributes / InferCreationAttributes derive the shapes for reading and[!TIP]
Working through this for the first time? prorm in
TypeScript is the short version, with a model you
can paste and run.
import {
Model, DataTypes, InferAttributes, InferCreationAttributes,
CreationOptional, NonAttribute, ForeignKey,
} from 'ts-prorm-orm';
class User extends Model<InferAttributes<User>, InferCreationAttributes<User>> {
declare id: CreationOptional<number>; // optional on create, present on read
declare email: string;
declare bio: string | null; // nullable column
declare createdAt: CreationOptional<Date>;
declare posts?: NonAttribute<Post[]>; // an association, not a column
declare fullName: NonAttribute<string>; // a getter, not a column
}Model takes two type parameters: the attributes of a loaded row, and thecreate() / build(). The intent is that[!NOTE]
Releases before this fix mapped every property ofInferAttributes/InferCreationAttributestonever. Both now produce the shapes above.
Writing the two shapes out and deriving the creation one withPartialBy
still works, if you prefer it spelled out:
import { Model, type PartialBy, type InferAttributes } from 'ts-prorm-orm';
export interface AccountAttributes {
id: number;
email: string;
bio: string | null;
createdAt: Date;
}
// id and createdAt are filled in by the database.
export type NewAccount = PartialBy<AccountAttributes, 'id' | 'createdAt'>;
export class Account extends Model<AccountAttributes, NewAccount> {
declare id: number;
declare email: string;
declare bio: string | null;
declare createdAt: Date;
}
const draft: NewAccount = { email: 'ada@example.com', bio: null }; // id may be omitted
// @ts-expect-error: email is required on create
const missingEmail: NewAccount = { bio: null };
// @ts-expect-error: bio is nullable, not optional; say null explicitly
const missingBio: NewAccount = { email: 'ada@example.com' };
// InferAttributes keeps each attribute's own type.
const inferredEmail: InferAttributes<Account>['email'] = 'ada@example.com';
// @ts-expect-error: email is a string
const wrongEmail: InferAttributes<Account>['email'] = 42;@ts-expect-error line above is a real compile error, and the docs build| Type | Use |
|---|---|
CreationOptional<T> | Optional at create time, always present when read — auto-increment ids, defaulted columns, timestamps. |
NonAttribute<T> | Marks a property that is not a database column — eager-loaded associations, getters, computed values. Excluded from both inferred shapes. |
ForeignKey<T> | Ties a foreign-key column to the key it references: declare userId: ForeignKey<User['id']>. Transparent — the property is that key's type, so it reads and assigns like any other column. |
InferAttributes<T> | Every property except methods and NonAttribute ones, each with its own type. |
InferCreationAttributes<T> | Same, with CreationOptional and ? properties optional and the rest required. |
PartialBy<T, K> | T with K optional. |
RequiredBy<T, K> | T with K required. |
ModelStatic<T> | The static side of a model: the metadata (name, tableName, rawAttributes, associations) and everything you call on it — create, findAll, destroy, the aggregates, hasMany/belongsTo, the schema operations. Use it to type a model variable, or a function that takes any model. |
ModelOptions | The options object accepted by define() / @Table. |
declare keyword matters. Without it — under useDefineForClassFields,target: "ES2022" on — TypeScript emits a real class field,declare is a type-only declaration and emitsimport type { ModelStatic } from 'ts-prorm-orm';
async function purge<T extends { createdAt: Date }>(
model: ModelStatic<T>,
before: Date,
): Promise<number> {
return model.destroy({ where: { createdAt: { lt: before } } });
}import type { ModelStatic, FindOptions } from 'ts-prorm-orm';
export interface Page<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
export async function paginate<T>(
model: ModelStatic<T>,
page: number,
pageSize = 20,
options: FindOptions = {},
): Promise<Page<T>> {
const { rows, count } = await model.findAndCountAll({
...options,
limit: pageSize,
offset: (page - 1) * pageSize,
});
return { items: rows as T[], total: count, page, pageSize };
}const page2 = await paginate(User, 2, 25, { where: { status: 'active' }, order: [['id', 'ASC']] });
page2.items.length; // at most 25import type {
PrormOptions, FindOptions, WhereOptions, Order, Includeable,
AttributeOptions, IndexOptions, ConstraintOptions,
CreateOptions, UpdateOptions, DestroyOptions, BulkCreateOptions,
TransactionOptions, SyncOptions, QueryOptions,
} from 'ts-prorm-orm';
const findActive: FindOptions = {
where: { status: 'active' },
order: [['createdAt', 'DESC']],
limit: 20,
};import type { FindOptions } from 'ts-prorm-orm';
function userSearch(q: { role?: string; newestFirst?: boolean; limit?: number }): FindOptions {
const options: FindOptions = { where: {}, limit: Math.min(q.limit ?? 20, 100) };
if (q.role) options.where = { ...(options.where as object), role: q.role };
if (q.newestFirst) options.order = [['createdAt', 'DESC']];
return options;
}
const admins = await User.findAll(userSearch({ role: 'admin', newestFirst: true }));tsconfig{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true
}
}strict: true is what makes CreationOptional and the nullable column typesstrictNullChecks, string | null and string areInferAttributes does not consult your data types. It reads thedeclare age: string besideage: { type: DataTypes.INTEGER() } compiles happily and hands you a number at @Attribute(DataTypes.INTEGER())
- declare age: string; // compiles; row.age is a number at runtime
+ declare age: number;NonAttribute<T> is T. It is a marker for the inference types, not a