Marcelo Kapi

1589397720

A practical guide to TypeScript decorators

We can all agree that JavaScript is an amazing programming language that allows you to build apps on almost any platform. Although it comes with its own fair share of drawbacks, TypeScript has done a great job of covering up some gaps inherent in JavaScript. Not only does it add type safety to a dynamic language, but it also comes with some cool features that don’t exist yet in JavaScript, such as decorators.

#Uncategorized #javascript #typescript

What is GEEK

Buddha Community

A practical guide to TypeScript decorators

The Definitive Guide to TypeScript & Possibly The Best TypeScript Book

TypeScript Deep Dive

I've been looking at the issues that turn up commonly when people start using TypeScript. This is based on the lessons from Stack Overflow / DefinitelyTyped and general engagement with the TypeScript community. You can follow for updates and don't forget to ★ on GitHub 🌹

Reviews

  • Thanks for the wonderful book. Learned a lot from it. (link)
  • Its probably the Best TypeScript book out there. Good Job (link)
  • Love how precise and clear the examples and explanations are! (link)
  • For the low, low price of free, you get pages of pure awesomeness. Chock full of source code examples and clear, concise explanations, TypeScript Deep Dive will help you learn TypeScript development. (link)
  • Just a big thank you! Best TypeScript 2 detailed explanation! (link)
  • This gitbook got my project going pronto. Fluent easy read 5 stars. (link)
  • I recommend the online #typescript book by @basarat you'll love it.(link)
  • I've always found this by @basarat really helpful. (link)
  • We must highlight TypeScript Deep Dive, an open source book.(link)
  • Great online resource for learning. (link)
  • Thank you for putting this book together, and for all your hard work within the TypeScript community. (link)
  • TypeScript Deep Dive is one of the best technical texts I've read in a while. (link)
  • Thanks @basarat for the TypeScript Deep Dive Book. Help me a lot with my first TypeScript project. (link)
  • Thanks to @basarat for this great #typescript learning resource. (link)
  • Guyz excellent book on Typescript(@typescriptlang) by @basarat (link)
  • Leaning on the legendary @basarat's "TypeScript Deep Dive" book heavily at the moment (link)
  • numTimesPointedPeopleToBasaratsTypeScriptBook++; (link)
  • A book not only for typescript, a good one for deeper JavaScript knowledge as well. link
  • In my new job, we're using @typescriptlang, which I am new to. This is insanely helpful huge thanks, @basarat! link
  • Thank you for writing TypeScript Deep Dive. I have learned so much. link
  • Loving @basarat's @typescriptlang online book basarat.gitbooks.io/typescript/# loaded with great recipes! link
  • Microsoft doc is great already, but if want to "dig deeper" into TypeScript I find this book of great value link
  • Thanks, this is a great book 🤓🤓 link
  • Deep dive to typescript is awesome in so many levels. i find it very insightful. Thanks link
  • @basarat's intro to @typescriptlang is still one of the best going (if not THE best) link
  •  
  • This is sweet! So many #typescript goodies! link

Get Started

If you are here to read the book online get started.

Translations

Book is completely free so you can copy paste whatever you want without requiring permission. If you have a translation you want me to link here. Send a PR.

Other Options

You can also download one of the Epub, Mobi, or PDF formats from the actions tab by clicking on the latest build run. You will find the files in the artifacts section.

Special Thanks

All the amazing contributors 🌹

Share

Share URL: https://basarat.gitbook.io/typescript/

Author: Basarat
Source Code: https://github.com/basarat/typescript-book/ 
License: View license

#typescript #opensource 

Marcelo Kapi

1589397720

A practical guide to TypeScript decorators

We can all agree that JavaScript is an amazing programming language that allows you to build apps on almost any platform. Although it comes with its own fair share of drawbacks, TypeScript has done a great job of covering up some gaps inherent in JavaScript. Not only does it add type safety to a dynamic language, but it also comes with some cool features that don’t exist yet in JavaScript, such as decorators.

#Uncategorized #javascript #typescript

Decorators between Class-based & Prototype-based programming languages

The difference between OOP and Prototype languages centralize around the reuse principle (Known as inheritance), its performed via a process of reusing objects (Known as prototype inheritance) in Prototype-based programming languages and via a process of reusing the classes of objects or blueprints of objects (Known as class inheritance).

The decorator is a design pattern that allows behavior to be added to an object in a dynamic way without having to change the implementation of that object. The decorator is considered as Structural design pattern. And an important note is that the decorator follows the Open-Closed Principle.

Implementing the decorator pattern will differ between Class-based and Prototype-based programming languages because of the difference in their nature. I will talk in this article about implementing the decorator pattern in both Javascript and C## programming languages. And consider some real-life scenarios to use them.


Decorator in C#

Decorators in C## can be applied using two methods:

1- Inheriting from a target class and have a setter method to set that target class instance inside the decorator or bypassing the target class as a dependency to the decorator class through the constructor. Then in the overridden functions, we call the target class function with additional decoration. The next UML explains that:

Image for post

#javascript #decorators #typescript #c-sharp-programming #decorator-pattern

Makenzie  Pagac

Makenzie Pagac

1605668648

Your Reference Guide to Using TypeScript in React

One of the issues with JavaScript is its dynamically-typed nature, meaning that data and variable types are unknown until runtime. This can have many side effects. For example, it can cause confusion within your codebase due to the fact that a variable can be anything.

To solve this issue, Microsoft released TypeScript. Anders Hejlsberg, lead architect of TypeScript, said, “What if we could strengthen JavaScript with the things that are missing for large scale application development, like static typing, classes [and] modules…? That’s what TypeScript is about.”

TypeScript instantly became the most widely used static-typed version of JavaScript. It enabled JavaScript devs to statically type data and variables. Soon enough, it was introduced to React.js, enabling React devs to write their React app in TypeScript.

But this comes at a cost: TypeScript typings and syntaxes can be difficult to keep up with, especially when used with React.

React has many features, such as props, class components, function components, function params, components lifecycle hooks, and member properties. Because typing these within TypeScript isn’t easy, this article aims to serve as a quick reference and learning guide for both beginner and advanced React devs.

With it, you’ll be able to quickly look up best practices and generic TS types in React. Ready? Let’s get started.

TypeScript typings

TypeScript has a typings folder where it saves files with *.d.ts extension. These files include interfaces that infer what shape a value will take. This is what enables TypeScript to bring data-typing to JavaScript.

An interface describes what a value would look like:

type AppState {
    propOne: number;
    propTwo: string
}

AppState describes what the value of its data-type would look like. First, we infer it would be an object that holds properties propOne and propTwo, the former being of number type and the latter being a string type. Assigning a boolean type to propOne would cause TypeScript to throw TypeError.

When we include TypeScript in our React project, every React element has an interface that defines the shape it will take. Let’s start with function component.

Function component

Function components are normal functions that return JSX elements in React and are used to create views. Initially, they are stateless components, but with the arrival of React hooks, they can be made stateful and smart/

Defining a React function component takes the React.FunctionComponent shape:

function App: React.FunctionComponent<> {
    return (
        <>
            // ...
        </>
    )
}

We can also use the shorthand React.FC:

function App: React.FC<> {
    return (
        <>
            // ...
        </>
    )
}

React.FunctionComponent, or React.FC, describes explicitly the return type of the function component.

We can type the props definitions in the arrows <>.

type AppProps = {
    message: string;
    age: number;
};

AppProps is the interface the props passed to App will take, so we can write the App component below if it would receive props:

type AppProps {
    message: string;
    age: number;
}

function App: React.FC<AppProps>(props: AppProps) {
    return (
        <>
            // ...
        </>
    )
}

We can use ? to set optional values in the typing:

type AppProps {
    message: string;
    age?: number;
}

Now, the age property becomes optional. The App component can be rendered, omitting the age property in its props object.

We can omit the type declaration within the arrows <>.

function App<{message: string; age: number;}>({message: string; age: number;}: AppProps) {
    // ...
}

Inner functions in the functional component can be typed, like so:

function App<{message: string; age: number;}>({message: string; age: number;}: AppProps) {
    // ...

    function clickHandler (val: number) {
        // ...
    }

    return (
        <>
            <button onClick={() => clickHandler(45)}            
        </>
    )
}

#typescript #typescript #javascript #web-development #developer

Lawrence  Lesch

Lawrence Lesch

1673040180

Sequelize-typescript: Decorators and Some Other Features for Sequelize

Sequelize-typescript

Decorators and some other features for sequelize (v6).

Installation

npm install --save-dev @types/node @types/validator
npm install sequelize reflect-metadata sequelize-typescript

Your tsconfig.json needs the following flags:

"target": "es6", // or a more recent ecmascript version
"experimentalDecorators": true,
"emitDecoratorMetadata": true

Sequelize Options

  • SequelizeConfig renamed to SequelizeOptions
  • modelPaths property renamed to models

Scopes Options

The @Scopes and @DefaultScope decorators now take lambda's as options

@DefaultScope(() => ({...}))
@Scopes(() => ({...}))

instead of deprecated way:

@DefaultScope({...})
@Scopes({...}))

Model definition

import { Table, Column, Model, HasMany } from 'sequelize-typescript';

@Table
class Person extends Model {
  @Column
  name: string;

  @Column
  birthday: Date;

  @HasMany(() => Hobby)
  hobbies: Hobby[];
}

Less strict

import { Table, Model } from 'sequelize-typescript';

@Table
class Person extends Model {}

More strict

import { Optional } from 'sequelize';
import { Table, Model } from 'sequelize-typescript';

interface PersonAttributes {
  id: number;
  name: string;
}

interface PersonCreationAttributes extends Optional<PersonAttributes, 'id'> {}

@Table
class Person extends Model<PersonAttributes, PersonCreationAttributes> {}

The model needs to extend the Model class and has to be annotated with the @Table decorator. All properties that should appear as a column in the database require the @Column annotation.

See more advanced example here.

@Table

The @Table annotation can be used without passing any parameters. To specify some more define options, use an object literal (all define options from sequelize are valid):

@Table({
  timestamps: true,
  ...
})
class Person extends Model {}

Table API

DecoratorDescription
@Tablesets options.tableName=<CLASS_NAME> and options.modelName=<CLASS_NAME> automatically
@Table(options: DefineOptions)sets define options (also sets options.tableName=<CLASS_NAME> and options.modelName=<CLASS_NAME> if not already defined by define options)

Primary key

A primary key (id) will be inherited from base class Model. This primary key is by default an INTEGER and has autoIncrement=true (This behaviour is a native sequelize thing). The id can easily be overridden by marking another attribute as primary key. So either set @Column({primaryKey: true}) or use @PrimaryKey together with @Column.

@CreatedAt, @UpdatedAt, @DeletedAt

Annotations to define custom and type safe createdAt, updatedAt and deletedAt attributes:

  @CreatedAt
  creationDate: Date;

  @UpdatedAt
  updatedOn: Date;

  @DeletedAt
  deletionDate: Date;
DecoratorDescription
@CreatedAtsets timestamps=true and createdAt='creationDate'
@UpdatedAtsets timestamps=true and updatedAt='updatedOn'
@DeletedAtsets timestamps=true, paranoid=true and deletedAt='deletionDate'

@Column

The @Column annotation can be used without passing any parameters. But therefore it is necessary that the js type can be inferred automatically (see Type inference for details).

  @Column
  name: string;

If the type cannot or should not be inferred, use:

import {DataType} from 'sequelize-typescript';

  @Column(DataType.TEXT)
  name: string;

Or for a more detailed column description, use an object literal (all attribute options from sequelize are valid):

  @Column({
    type: DataType.FLOAT,
    comment: 'Some value',
    ...
  })
  value: number;

Column API

DecoratorDescription
@Columntries to infer dataType from js type
@Column(dataType: DataType)sets dataType explicitly
@Column(options: AttributeOptions)sets attribute options

Shortcuts

If you're in love with decorators: sequelize-typescript provides some more of them. The following decorators can be used together with the @Column annotation to make some attribute options easier available:

DecoratorDescriptionOptions
@AllowNull(allowNull?: boolean)sets attribute.allowNull (default is true) 
@AutoIncrementsets attribute.autoIncrement=true 
@Unique(options? UniqueOptions)sets attribute.unique=trueUniqueOptions
@Default(value: any)sets attribute.defaultValue to specified value 
@PrimaryKeysets attribute.primaryKey=true 
@Comment(value: string)sets attribute.comment to specified string 
Validate annotationssee Model validation 

Type inference

The following types can be automatically inferred from javascript type. Others have to be defined explicitly.

Design typeSequelize data type
stringSTRING
booleanBOOLEAN
numberINTEGER
bigintBIGINT
DateDATE
BufferBLOB

Accessors

Get/set accessors do work as well

@Table
class Person extends Model {
  @Column
  get name(): string {
    return 'My name is ' + this.getDataValue('name');
  }

  set name(value: string) {
    this.setDataValue('name', value);
  }
}

Usage

Except for minor variations sequelize-typescript will work like pure sequelize. (See sequelize docs)

Configuration

To make the defined models available, you have to configure a Sequelize instance from sequelize-typescript(!).

import { Sequelize } from 'sequelize-typescript';

const sequelize = new Sequelize({
  database: 'some_db',
  dialect: 'sqlite',
  username: 'root',
  password: '',
  storage: ':memory:',
  models: [__dirname + '/models'], // or [Player, Team],
});

Before you can use your models you have to tell sequelize where they can be found. So either set models in the sequelize config or add the required models later on by calling sequelize.addModels([Person]) or sequelize.addModels([__dirname + '/models']):

sequelize.addModels([Person]);
sequelize.addModels(['path/to/models']);

globs

import {Sequelize} from 'sequelize-typescript';

const sequelize =  new Sequelize({
        ...
        models: [__dirname + '/**/*.model.ts']
});
// or
sequelize.addModels([__dirname + '/**/*.model.ts']);

Model-path resolving

A model is matched to a file by its filename. E.g.

// File User.ts matches the following exported model.
export class User extends Model {}

This is done by comparison of the filename against all exported members. The matching can be customized by specifying the modelMatch function in the configuration object.

For example, if your models are named user.model.ts, and your class is called User, you can match these two by using the following function:

import {Sequelize} from 'sequelize-typescript';

const sequelize =  new Sequelize({
  models: [__dirname + '/models/**/*.model.ts']
  modelMatch: (filename, member) => {
    return filename.substring(0, filename.indexOf('.model')) === member.toLowerCase();
  },
});

For each file that matches the *.model.ts pattern, the modelMatch function will be called with its exported members. E.g. for the following file

//user.model.ts
import {Table, Column, Model} from 'sequelize-typescript';

export const UserN = 'Not a model';
export const NUser = 'Not a model';

@Table
export class User extends Model {

  @Column
  nickname: string;
}

The modelMatch function will be called three times with the following arguments.

user.model UserN -> false
user.model NUser -> false
user.model User  -> true (User will be added as model)

Another way to match model to file is to make your model the default export.

export default class User extends Model {}

⚠️ When using paths to add models, keep in mind that they will be loaded during runtime. This means that the path may differ from development time to execution time. For instance, using .ts extension within paths will only work together with ts-node.

Build and create

Instantiation and inserts can be achieved in the good old sequelize way

const person = Person.build({ name: 'bob', age: 99 });
person.save();

Person.create({ name: 'bob', age: 99 });

but sequelize-typescript also makes it possible to create instances with new:

const person = new Person({ name: 'bob', age: 99 });
person.save();

Find and update

Finding and updating entries does also work like using native sequelize. So see sequelize docs for more details.

Person.findOne().then((person) => {
  person.age = 100;
  return person.save();
});

Person.update(
  {
    name: 'bobby',
  },
  { where: { id: 1 } }
).then(() => {});

Model association

Relations can be described directly in the model by the @HasMany, @HasOne, @BelongsTo, @BelongsToMany and @ForeignKey annotations.

One-to-many

@Table
class Player extends Model {
  @Column
  name: string;

  @Column
  num: number;

  @ForeignKey(() => Team)
  @Column
  teamId: number;

  @BelongsTo(() => Team)
  team: Team;
}

@Table
class Team extends Model {
  @Column
  name: string;

  @HasMany(() => Player)
  players: Player[];
}

That's all, sequelize-typescript does everything else for you. So when retrieving a team by find

Team.findOne({ include: [Player] }).then((team) => {
  team.players.forEach((player) => console.log(`Player ${player.name}`));
});

the players will also be resolved (when passing include: Player to the find options)

Many-to-many

@Table
class Book extends Model {
  @BelongsToMany(() => Author, () => BookAuthor)
  authors: Author[];
}

@Table
class Author extends Model {
  @BelongsToMany(() => Book, () => BookAuthor)
  books: Book[];
}

@Table
class BookAuthor extends Model {
  @ForeignKey(() => Book)
  @Column
  bookId: number;

  @ForeignKey(() => Author)
  @Column
  authorId: number;
}

Type safe through-table instance access

To access the through-table instance (instanceOf BookAuthor in the upper example) type safely, the type need to be set up manually. For Author model it can be achieved like so:

  @BelongsToMany(() => Book, () => BookAuthor)
  books: Array<Book & {BookAuthor: BookAuthor}>;

One-to-one

For one-to-one use @HasOne(...)(foreign key for the relation exists on the other model) and @BelongsTo(...) (foreign key for the relation exists on this model)

@ForeignKey, @BelongsTo, @HasMany, @HasOne, @BelongsToMany API

DecoratorDescription
@ForeignKey(relatedModelGetter: () => typeof Model)marks property as foreignKey for related class
@BelongsTo(relatedModelGetter: () => typeof Model)sets SourceModel.belongsTo(RelatedModel, ...) while as is key of annotated property and foreignKey is resolved from source class
@BelongsTo(relatedModelGetter: () => typeof Model, foreignKey: string)sets SourceModel.belongsTo(RelatedModel, ...) while as is key of annotated property and foreignKey is explicitly specified value
@BelongsTo(relatedModelGetter: () => typeof Model, options: AssociationOptionsBelongsTo)sets SourceModel.belongsTo(RelatedModel, ...) while as is key of annotated property and options are additional association options
@HasMany(relatedModelGetter: () => typeof Model)sets SourceModel.hasMany(RelatedModel, ...) while as is key of annotated property and foreignKey is resolved from target related class
@HasMany(relatedModelGetter: () => typeof Model, foreignKey: string)sets SourceModel.hasMany(RelatedModel, ...) while as is key of annotated property and foreignKey is explicitly specified value
@HasMany(relatedModelGetter: () => typeof Model, options: AssociationOptionsHasMany)sets SourceModel.hasMany(RelatedModel, ...) while as is key of annotated property and options are additional association options
@HasOne(relatedModelGetter: () => typeof Model)sets SourceModel.hasOne(RelatedModel, ...) while as is key of annotated property and foreignKey is resolved from target related class
@HasOne(relatedModelGetter: () => typeof Model, foreignKey: string)sets SourceModel.hasOne(RelatedModel, ...) while as is key of annotated property and foreignKey is explicitly specified value
@HasOne(relatedModelGetter: () => typeof Model, options: AssociationOptionsHasOne)sets SourceModel.hasOne(RelatedModel, ...) while as is key of annotated property and options are additional association options
@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model))sets SourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...}) while as is key of annotated property and foreignKey/otherKey is resolved from through class
@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model), foreignKey: string)sets SourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...}) while as is key of annotated property, foreignKey is explicitly specified value and otherKey is resolved from through class
@BelongsToMany(relatedModelGetter: () => typeof Model, through: (() => typeof Model), foreignKey: string, otherKey: string)sets SourceModel.belongsToMany(RelatedModel, {through: ThroughModel, ...}) while as is key of annotated property and foreignKey/otherKey are explicitly specified values
@BelongsToMany(relatedModelGetter: () => typeof Model, through: string, foreignKey: string, otherKey: string)sets SourceModel.belongsToMany(RelatedModel, {through: throughString, ...}) while as is key of annotated property and foreignKey/otherKey are explicitly specified values
@BelongsToMany(relatedModelGetter: () => typeof Model, options: AssociationOptionsBelongsToMany)sets SourceModel.belongsToMany(RelatedModel, {through: throughString, ...}) while as is key of annotated property and options are additional association values, including foreignKey and otherKey.

Note that when using AssociationOptions, certain properties will be overwritten when the association is built, based on reflection metadata or explicit attribute parameters. For example, as will always be the annotated property's name, and through will be the explicitly stated value.

Multiple relations of same models

sequelize-typescript resolves the foreign keys by identifying the corresponding class references. So if you define a model with multiple relations like

@Table
class Book extends Model {
  @ForeignKey(() => Person)
  @Column
  authorId: number;

  @BelongsTo(() => Person)
  author: Person;

  @ForeignKey(() => Person)
  @Column
  proofreaderId: number;

  @BelongsTo(() => Person)
  proofreader: Person;
}

@Table
class Person extends Model {
  @HasMany(() => Book)
  writtenBooks: Book[];

  @HasMany(() => Book)
  proofedBooks: Book[];
}

sequelize-typescript cannot know which foreign key to use for which relation. So you have to add the foreign keys explicitly:


  // in class "Books":
  @BelongsTo(() => Person, 'authorId')
  author: Person;

  @BelongsTo(() => Person, 'proofreaderId')
  proofreader: Person;

  // in class "Person":
  @HasMany(() => Book, 'authorId')
  writtenBooks: Book[];

  @HasMany(() => Book, 'proofreaderId')
  proofedBooks: Book[];

Type safe usage of auto generated functions

With the creation of a relation, sequelize generates some method on the corresponding models. So when you create a 1:n relation between ModelA and ModelB, an instance of ModelA will have the functions getModelBs, setModelBs, addModelB, removeModelB, hasModelB. These functions still exist with sequelize-typescript. But TypeScript wont recognize them and will complain if you try to access getModelB, setModelB or addModelB. To make TypeScript happy, the Model.prototype of sequelize-typescript has $set, $get, $add functions.

@Table
class ModelA extends Model {
  @HasMany(() => ModelB)
  bs: ModelB[];
}

@Table
class ModelB extends Model {
  @BelongsTo(() => ModelA)
  a: ModelA;
}

To use them, pass the property key of the respective relation as the first parameter:

const modelA = new ModelA();

modelA
  .$set('bs', [
    /* instance */
  ])
  .then(/* ... */);
modelA.$add('b' /* instance */).then(/* ... */);
modelA.$get('bs').then(/* ... */);
modelA.$count('bs').then(/* ... */);
modelA.$has('bs').then(/* ... */);
modelA.$remove('bs' /* instance */).then(/* ... */);
modelA.$create('bs' /* value */).then(/* ... */);

Indexes

@Index

The @Index annotation can be used without passing any parameters.

@Table
class Person extends Model {
  @Index // Define an index with default name
  @Column
  name: string;

  @Index // Define another index
  @Column
  birthday: Date;
}

To specify index and index field options, use an object literal (see indexes define option):

@Table
class Person extends Model {
  @Index('my-index') // Define a multi-field index on name and birthday
  @Column
  name: string;

  @Index('my-index') // Add birthday as the second field to my-index
  @Column
  birthday: Date;

  @Index({
    // index options
    name: 'job-index',
    parser: 'my-parser',
    type: 'UNIQUE',
    unique: true,
    where: { isEmployee: true },
    concurrently: true,
    using: 'BTREE',
    operator: 'text_pattern_ops',
    prefix: 'test-',
    // index field options
    length: 10,
    order: 'ASC',
    collate: 'NOCASE',
  })
  @Column
  jobTitle: string;

  @Column
  isEmployee: boolean;
}

Index API

DecoratorDescription
@Indexadds new index on decorated field to options.indexes
@Index(name: string)adds new index or adds the field to an existing index with specified name
@Table(options: IndexDecoratorOptions)sets both index and index field options

createIndexDecorator()

The createIndexDecorator() function can be used to create a decorator for an index with options specified with an object literal supplied as the argument. Fields are added to the index by decorating properties.

const SomeIndex = createIndexDecorator();
const JobIndex = createIndexDecorator({
  // index options
  name: 'job-index',
  parser: 'my-parser',
  type: 'UNIQUE',
  unique: true,
  where: { isEmployee: true },
  concurrently: true,
  using: 'BTREE',
  operator: 'text_pattern_ops',
  prefix: 'test-',
});

@Table
class Person extends Model {
  @SomeIndex // Add name to SomeIndex
  @Column
  name: string;

  @SomeIndex // Add birthday to SomeIndex
  @Column
  birthday: Date;

  @JobIndex({
    // index field options
    length: 10,
    order: 'ASC',
    collate: 'NOCASE',
  })
  @Column
  jobTitle: string;

  @Column
  isEmployee: boolean;
}

Repository mode

With sequelize-typescript@1 comes a repository mode. See docs for details.

The repository mode makes it possible to separate static operations like find, create, ... from model definitions. It also empowers models so that they can be used with multiple sequelize instances.

How to enable repository mode?

Enable repository mode by setting repositoryMode flag:

const sequelize = new Sequelize({
  repositoryMode: true,
  ...,
});

How to use repository mode?

Retrieve repository to create instances or perform search operations:

const userRepository = sequelize.getRepository(User);

const luke = await userRepository.create({ name: 'Luke Skywalker' });
const luke = await userRepository.findOne({ where: { name: 'luke' } });

How to use associations with repository mode?

For now one need to use the repositories within the include options in order to retrieve or create related data:

const userRepository = sequelize.getRepository(User);
const addressRepository = sequelize.getRepository(Address);

userRepository.find({ include: [addressRepository] });
userRepository.create({ name: 'Bear' }, { include: [addressRepository] });

⚠️ This will change in the future: One will be able to refer the model classes instead of the repositories.

Limitations of repository mode

Nested scopes and includes in general won't work when using @Scope annotation together with repository mode like:

@Scopes(() => ({
  // includes
  withAddress: {
    include: [() => Address],
  },
  // nested scopes
  withAddressIncludingLatLng: {
    include: [() => Address.scope('withLatLng')],
  },
}))
@Table
class User extends Model {}

⚠️ This will change in the future: Simple includes will be implemented.

Model validation

Validation options can be set through the @Column annotation, but if you prefer to use separate decorators for validation instead, you can do so by simply adding the validate options as decorators: So that validate.isEmail=true becomes @IsEmail, validate.equals='value' becomes @Equals('value') and so on. Please notice that a validator that expects a boolean is translated to an annotation without a parameter.

See sequelize docs for all validators.

Exceptions

The following validators cannot simply be translated from sequelize validator to an annotation:

ValidatorAnnotation
validate.len=[number, number]@Length({max?: number, min?: number})
validate[customName: string]For custom validators also use the @Is(...) annotation: Either @Is('custom', (value) => { /* ... */}) or with named function @Is(function custom(value) { /* ... */})

Example

const HEX_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;

@Table
export class Shoe extends Model {
  @IsUUID(4)
  @PrimaryKey
  @Column
  id: string;

  @Equals('lala')
  @Column
  readonly key: string;

  @Contains('Special')
  @Column
  special: string;

  @Length({ min: 3, max: 15 })
  @Column
  brand: string;

  @IsUrl
  @Column
  brandUrl: string;

  @Is('HexColor', (value) => {
    if (!HEX_REGEX.test(value)) {
      throw new Error(`"${value}" is not a hex color value.`);
    }
  })
  @Column
  primaryColor: string;

  @Is(function hexColor(value: string): void {
    if (!HEX_REGEX.test(value)) {
      throw new Error(`"${value}" is not a hex color value.`);
    }
  })
  @Column
  secondaryColor: string;

  @Is(HEX_REGEX)
  @Column
  tertiaryColor: string;

  @IsDate
  @IsBefore('2017-02-27')
  @Column
  producedAt: Date;
}

Scopes

Scopes can be defined with annotations as well. The scope options are identical to native sequelize (See sequelize docs for more details)

@DefaultScope and @Scopes

@DefaultScope(() => ({
  attributes: ['id', 'primaryColor', 'secondaryColor', 'producedAt'],
}))
@Scopes(() => ({
  full: {
    include: [Manufacturer],
  },
  yellow: {
    where: { primaryColor: 'yellow' },
  },
}))
@Table
export class ShoeWithScopes extends Model {
  @Column
  readonly secretKey: string;

  @Column
  primaryColor: string;

  @Column
  secondaryColor: string;

  @Column
  producedAt: Date;

  @ForeignKey(() => Manufacturer)
  @Column
  manufacturerId: number;

  @BelongsTo(() => Manufacturer)
  manufacturer: Manufacturer;
}

Hooks

Hooks can be attached to your models. All Model-level hooks are supported. See the related unit tests for a summary.

Each hook must be a static method. Multiple hooks can be attached to a single method, and you can define multiple methods for a given hook.

The name of the method cannot be the same as the name of the hook (for example, a @BeforeCreate hook method cannot be named beforeCreate). That’s because Sequelize has pre-defined methods with those names.

@Table
export class Person extends Model {
  @Column
  name: string;

  @BeforeUpdate
  @BeforeCreate
  static makeUpperCase(instance: Person) {
    // this will be called when an instance is created or updated
    instance.name = instance.name.toLocaleUpperCase();
  }

  @BeforeCreate
  static addUnicorn(instance: Person) {
    // this will also be called when an instance is created
    instance.name += ' 🦄';
  }
}

Why () => Model?

@ForeignKey(Model) is much easier to read, so why is @ForeignKey(() => Model) so important? When it comes to circular-dependencies (which are in general solved by node for you) Model can be undefined when it gets passed to @ForeignKey. With the usage of a function, which returns the actual model, we prevent this issue.

Recommendations and limitations

One Sequelize instance per model (without repository mode)

Unless you are using the repository mode, you won't be able to add one and the same model to multiple Sequelize instances with differently configured connections. So that one model will only work for one connection.

One model class per file

This is not only good practice regarding design, but also matters for the order of execution. Since Typescript creates a __metadata("design:type", SomeModel) call due to emitDecoratorMetadata compile option, in some cases SomeModel is probably not defined(not undefined!) and would throw a ReferenceError. When putting SomeModel in a separate file, it would look like __metadata("design:type", SomeModel_1.SomeModel), which does not throw an error.

Minification

If you need to minify your code, you need to set tableName and modelName in the DefineOptions for @Table annotation. sequelize-typescript uses the class name as default name for tableName and modelName. When the code is minified the class name will no longer be the originally defined one (So that class User will become class b for example).

Contributing

To contribute you can:

  • Open issues and participate in discussion of other issues.
  • Fork the project to open up PR's.
  • Update the types of Sequelize.
  • Anything else constructively helpful.

In order to open a pull request please:

  • Create a new branch.
  • Run tests locally (npm install && npm run build && npm run cover) and ensure your commits don't break the tests.
  • Document your work well with commit messages, a good PR description, comments in code when necessary, etc.

In order to update the types for sequelize please go to the Definitely Typed repo, it would also be a good idea to open a PR into sequelize so that Sequelize can maintain its own types, but that might be harder than getting updated types into microsoft's repo. The Typescript team is slowly trying to encourage npm package maintainers to maintain their own typings, but Microsoft still has dedicated and good people maintaining the DT repo, accepting PR's and keeping quality high.

Keep in mind sequelize-typescript does not provide typings for sequelize - these are seperate things. A lot of the types in sequelize-typescript augment, refer to, or extend what sequelize already has.

Download Details:

Author: Sequelize
Source Code: https://github.com/sequelize/sequelize-typescript 
License: MIT license

#typescript #orm #decorators #sequelize