Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[RFC] Decorators #14298

Closed
Closed
Assignees
ephys
Labels
RFCRequest for comments regarding breaking or large changestypescriptFor issues and PRs. Things that involve typescript, such as typings and intellisense.
@ephys

Description

@ephys

This is a long one, bear with me.

I'd like to bring built-in support for decorator based model declaration in Sequelize. It's one of the main arguments I've seen in favor of using TypeORM over the years and I think it would help reduce the boilerplate of creating a new model.

I am not going to lie, a lot of this is very inspired bysequelize-typescript, with some differences.

Prior art

Foreword: Legacy Decorators & Stage 2 Decorators

Both the TypeScript & Babel communities have been using Decorators for years. These decorators follow the old decorator spec.A new spec has been in the works for a few years now. It is unclear when stable decorators will actually land in ECMAScript.

As such, I propose to do the initial implementation using Legacy Decorators but expose them throughsequelize/decorators-legacy. Once decorators become stage 4, we can do a parallel implementation that is directly exposed in the rootsequelize import, and deprecate/decorators-legacy.

API Design

Model registration

One of the first parts of the design would be to provide a way to register a model that has been decorated. A basic building block that's decorator-implementation agnostic. Both for existing third-party packages and a future stage-4 decorators implementation.

I would expose two methods:registerModelAttributeOptions &registerModelOptions:

/** * Registers attribute options for future registering of the Model using Model.init * Subsequent calls for the same model & attributeName will be merged, with the newer call taking precedence. */registerModelAttributeOptions(model:typeofModel,attributeName: string,options:Partial<ModelAttributeColumnOptions>):void;/** * Registers model options for future registering of the Model using Model.init * Subsequent calls for the same model & attributeName will be merged, with the newer call taking precedence. * 'sequelize' option is not accepted here. Pass it through `Model.init` when registering the model. */registerModelOptions(model:typeofModel,options:Partial<ModelOptions>):void;

We then also need a way to register the model to Sequelize. I see a few options:

  • OverloadModel.init:
    classModel{// use this one when registering a decorated model.staticinit(options:{sequelize:Sequelize});// use this one when registering a non-decorated model.staticinit(attributes:ModelAttributes,options:InitOptions);}
  • OverloadSequelize#define
  • AddSequelize#registerModels
  • Add amodels parameter to the Sequelize constructor.

I'd opt for overloading.

Automatic model registration

Similarly to what TypeORM & SequelizeTypescript are doing, we could add an async methodSequelize#importModels(glob) that loads files matching the glob (using ESM dynamic import), and register any export that extends model and isn't tagged asabstract (see Model Inheritance).

Model Inheritance

this would resolve#1243

A big benefit of decorator-based definition is that it becomes possible to inherit definitions (both model options & attributes):

@ModelOptions({underscored:true,abstract:true,// do not actually register this model!})abstractclassBaseModelextendsModel{  @Column(DataType.TEXT,{unique:true,defaultValue:shortId(),field:'external_id'})publicId:string;  @Column(DataType.INTEGER,{primaryKey:true,autoIncrement:true,field:'private_id'})privateId:number;}// inherits 'publicId' & 'privateId' from BaseModelclassUserextendsMyBaseModel{  @Column(DataType.TEXT)name:string;}// inherits 'publicId' & 'privateId' from BaseModelclassProjectextendsMyBaseModel{  @Column(DataType.TEXT)name:string;}

Model Options Decorator:@ModelOptions

Note: Name is already taken by typing. Alternative names:@Model (already taken),@Table,@Entity, etc...

The simplest design for a Model Option decorator would be one that simply accepts the model options:

// abstract is for the creation of base models, see "inheritance"// the `abstract` tag is of course not itself inherited :)functionModelOptions(options:TModelOptions&{abstract?:boolean});@ModelOptions({tableName:'users'})classUserextendsMyBaseModel{}

Model Attribute Decorator

We have two choices here: A simpleAttribute decorator which acceptsModelAttributeColumnOptions, or a decorator per-option like withsequelize-typescript.

This is the design I came up with, critics and counter-proposals welcome:

typeTimestampAttributeOptions=Omit<ModelAttributeColumnOptions,'type'|'allowNull'|'unique'|'primaryKey'|'autoIncrement'|'defaultValue'|'autoIncrementIdentity'|'references'|'onUpdate'|'onDelete'>;// use one of these to define an attribute:functionCreatedAtAttribute(options?:TimestampAttributeOptions);functionUpdateAtAttribute(options?:TimestampAttributeOptions);functionDeletedAtAttribute(options?:TimestampAttributeOptions);functionAttribute(type:DataType,options?:Omit<ModelAttributeColumnOptions,'type'>);

Model Association Decorator

This part depends on RFC#14302 and is described over there but is basically: a decorator per association type:

import{Model,HasManyAttribute,HasMany}from"sequelize";classUserextendsModel{  @HasMany(Project)readonlyprojects!:HasManyAttribute<Project,number>;}classUserextendsModel{  @BelongsTo(User,{foreignKey:'authorId',onDelete:'CASCADE',})readonlyauthor!:BelongsToAttribute<User,number>;  @Attribute(DataType.number,{allowNull:false})authorId:number;}

emitDecoratorMetadata

I would vote against guessing whichDataType to use based on decorator metadata. Having defaults forstring,number, etc.. will lead to users accidentally using the Column Type as they're not strict enough.

other elements to consider

  • decorators for hooks
  • decorators for scopes
  • decorators for validators

Metadata

Metadata

Assignees

Labels

RFCRequest for comments regarding breaking or large changestypescriptFor issues and PRs. Things that involve typescript, such as typings and intellisense.

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions


    [8]ページ先頭

    ©2009-2025 Movatter.jp