TypeScript декораторы для ORM/ODM модуля @e22m4u/js-repository, позволяющие описывать модели данных, свойства и связи с помощью декларативного синтаксиса.
npm install @e22m4u/js-repository-decorators
Модуль js-repository также должен быть установлен.
npm install @e22m4u/js-repository
Для включения поддержки декораторов, добавьте указанные
ниже опции в файл tsconfig.json
вашего проекта.
{
"emitDecoratorMetadata": true,
"experimentalDecorators": true
}
- Объявление источника данных.
import {DatabaseSchema} from '@e22m4u/js-repository';
// создание экземпляра DatabaseSchema
const dbs = new DatabaseSchema();
// объявление константы для названия источника
const MY_DATABASE_1 = 'myDatabase1';
// объявление источника на примере MongoDB адаптера
// см. модуль @e22m4u/js-repository-mongodb-adapter
dbs.defineDatasource({
name: MY_DATABASE_1, // название нового источника
adapter: 'mongodb', // название адаптера
// параметры подключения
host: '127.0.0.1',
port: 27017,
database: 'myDatabase',
});
- Объявление моделей.
import {
DataType,
RelationType,
} from '@e22m4u/js-repository';
import {
model,
property,
relation,
} from '@e22m4u/js-repository-decorators';
// модель Role
@model({datasource: MY_DATABASE_1})
class Role {
@property({
type: DataType.STRING,
primaryKey: true,
})
id!: string;
@property(DataType.STRING)
name?: string;
}
// модель User
@model({datasource: MY_DATABASE_1})
class User {
@property({
type: DataType.STRING,
primaryKey: true,
})
id!: string;
@property({
type: DataType.STRING,
required: true,
})
name!: string;
@property({
type: DataType.STRING,
default: '',
})
roleId?: string;
@relation({
type: RelationType.BELONGS_TO,
model: Role.name,
})
role?: Role;
}
- Регистрация моделей.
import {getModelDefinitionFromClass} from '@e22m4u/js-repository-decorators';
// извлечение схем каждой модели
const roleDef = getModelDefinitionFromClass(Role);
const userDef = getModelDefinitionFromClass(User);
console.log(roleDef);
// {
// name: 'Role',
// tableName: 'roles',
// datasource: 'myDatabase1',
// properties: {
// id: {
// type: 'string',
// primaryKey: true
// },
// name: {
// type: 'string',
// required: true
// }
// }
// }
console.log(userDef);
// {
// name: 'User',
// tableName: 'users',
// datasource: 'myDatabase1',
// properties: {
// id: {
// type: 'string',
// primaryKey: true,
// },
// name: {
// type: 'string',
// required: true,
// },
// roleId: {
// type: 'string',
// default: '',
// },
// },
// relations: {
// role: {
// type: 'belongsTo',
// model: 'Role'
// },
// },
// },
// регистрация моделей в схеме базы
dbs.defineModel(roleDef);
dbs.defineModel(userDef);
- Операции с данными и разрешение связей.
// получение репозиториев
const roleRep = dbs.getRepository(Role.name);
const userRep = dbs.getRepository(User.name);
// создание новых документов
const manager = await roleRep.create({name: 'Manager'});
const user = await userRep.create({name: 'John', roleId: manager.id})
// извлечение документа и связанных данных
const userWithRole = await userRep.findById(user.id, {
include: 'role',
});
console.log(userWithRole);
// {
// "id": "64f3454e5e0893c13f9bf47e",
// "name": "John",
// "roleId": "64f34549bf47e3c13fe5e089",
// "role": {
// "id": "64f34549bf47e3c13fe5e089",
// "name": "Manager",
// }
// }
Декоратор применяется к классу и назначает его моделью определенной коллекции/таблицы в базе данных или встраиваемой частью других моделей.
import {model} from '@e22m4u/js-repository-decorators';
@model() // <=
class User {}
Опции (свойства объекта ModelOptions
).
- datasource - название источника данных;
- tableName - названия таблицы в базе данных;
Определение источника данных.
import {model} from '@e22m4u/js-repository-decorators';
@model({datasource: 'mongo'}) // <=
class User {}
Определение названия коллекции/таблицы в базе данных.
(по умолчанию используется имя класса)
import {model} from '@e22m4u/js-repository-decorators';
@model({tableName: 'users'}) // <=
class User {}
Декоратор применяется к свойству экземпляра класса, определяя тип допустимого значения.
DataType.ANY
- разрешено любое значение;DataType.STRING
- только значение типаstring
;DataType.NUMBER
- только значение типаnumber
;DataType.BOOLEAN
- только значение типаboolean
;DataType.ARRAY
- только значение типаarray
;DataType.OBJECT
- только значение типаobject
;
Пример:
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class User {
@property(DataType.STRING)
name?: string;
}
Кроме типа данных, первый аргумент декоратора может принимать объект PropertyDefinition
со следующими свойствами.
- type - тип допустимых значений;
- itemType - тип элемента (для массива);
- model - название модели (для объекта);
- primaryKey - первичный ключ;
- columnName - название колонки в базе данных;
- required - исключение
null
иundefined
; - default - значение по умолчанию;
- validate - проверка формата;
- transform - модификаторы значения;
- unique - проверка уникальности;
Тип передается первым аргументом или внутри объекта опций.
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class User {
@property({type: DataType.STRING}) // <=
surname?: string;
}
Определение типа для элемента массива.
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class Article {
@property({
type: DataType.ARRAY,
itemType: DataType.STRING, // <=
})
tags?: string[];
}
Определение модели объекта.
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class LatLng {
@property(DataType.NUMBER)
lat?: number;
@property(DataType.NUMBER)
lng?: number;
}
@model()
class Address {
@property({
type: DataType.OBJECT,
model: LatLng.name, // <=
})
coordinates?: LatLng;
}
Определение первичного ключа (по умолчанию свойство id
).
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class User {
@property({
type: DataType.STRING,
primaryKey: true, // <=
})
id!: string;
}
Определение названия колонки/свойства в базе данных.
(по умолчанию имя свойства)
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class User {
@property({
type: DataType.STRING,
columnName: 'NAME', // <=
})
name?: string;
}
Определение свойства обязательным (запрет пустых значений).
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class User {
@property({
type: DataType.STRING,
required: true, // <=
})
name!: string;
}
Определение значения по умолчанию.
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class User {
@property({
type: DataType.STRING,
default: 'John Doe', // <=
})
name?: string;
}
Использование предустановленных валидаторов.
minLength: number
- минимальная длина строки или массива;maxLength: number
- максимальная длина строки или массива;regexp: string | RegExp
- проверка по регулярному выражению;
import {DataType} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class User {
@property({
type: DataType.STRING,
validate: {
minLength: 2, // <=
maxLength: 24, // <=
},
})
name?: string;
}
i. Для регистрации пользовательских валидаторов см. раздел Валидаторы основного модуля.
Определение свойства уникальным.
import {DataType} from '@e22m4u/js-repository';
import {PropertyUniqueness} from '@e22m4u/js-repository';
import {property} from '@e22m4u/js-repository-decorators';
@model()
class User {
@property({
type: DataType.STRING,
unique: true, // <=
})
username!: string;
}
Режимы проверки.
PropertyUniqueness.STRICT
илиtrue
для строгой проверкиPropertyUniqueness.SPARSE
не проверять пустые значенияPropertyUniqueness.NON_UNIQUE
не проверять (по умолчанию)
Декоратор применяется к свойству экземпляра класса, определяя тип связи к целевой модели.
- Belongs To - ссылка через внешний ключ;
- Has One - обратная сторона Belongs To (один к одному);
- Has Many - обратная сторона Belongs To (один ко многим);
- References Many - ссылка через массив идентификаторов;
Полиморфные версии:
(модель определяется дискриминатором)
- Polymorphic Belongs To - ссылка через внешний ключ и дискриминатор;
- Polymorphic Has One - обратная сторона полиморфного Belongs To (один к одному);
- Polymorphic Has Many - обратная сторона полиморфного Belongs To (один ко многим);
Текущая модель ссылается на целевую используя внешний ключ.
import {RelationType} from '@e22m4u/js-repository';
import {model} from '@e22m4u/js-repository-decorators';
import {relation} from '@e22m4u/js-repository-decorators';
import {property} from '@e22m4u/js-repository-decorators';
// модель Role
@model()
class Role {
@property(DataType.STRING)
name?: string;
}
// модель User
@model()
class User {
@property(DataType.STRING)
name?: string;
@property(DataType.STRING)
roleId?: string;
@relation({
type: RelationType.BELONGS_TO, // <=
model: Role.name,
foreignKey: 'roleId', // (не обязательно)
// если "foreignKey" не указан, то название внешнего
// ключа формируется по названию связи с добавлением
// постфикса "Id"
})
role?: Role;
}
Документ Role.
{
"_id": "68a9c85f31f4414606e7da79",
"name": "Manager"
}
Документ User.
{
"_id": "68a9c9b52eab80fa02ee6ccb",
"name": "John Doe",
"roleId": "68a9c85f31f4414606e7da79"
}
Извлечение документа User и разрешение связи role
.
const user = await userRep.findOne({include: 'role'});
console.log(user);
// {
// id: '68a9c9b52eab80fa02ee6ccb',
// name: 'John Doe',
// roleId: '68a9c85f31f4414606e7da79',
// role: {
// id: '68a9c85f31f4414606e7da79',
// name: 'Manager'
// }
// }
Целевая модель ссылается на текущую по принципу один к одному.
(обратная сторона Belongs To)
import {RelationType} from '@e22m4u/js-repository';
import {model} from '@e22m4u/js-repository-decorators';
import {relation} from '@e22m4u/js-repository-decorators';
import {property} from '@e22m4u/js-repository-decorators';
// модель Profile
@model()
class Profile {
@property(DataType.STRING)
phone?: string;
@property(DataType.STRING)
address?: string;
@property(DataType.STRING)
userId?: string;
@relation({
type: RelationType.BELONGS_TO,
model: User.name,
// см. Belongs To
})
user?: User;
}
// модель User
@model()
class User {
@property(DataType.STRING)
name?: string;
@relation({
type: RelationType.HAS_ONE, // <=
model: Profile.name,
foreignKey: 'userId',
})
profile?: Profile;
}
Документ Profile.
{
"_id": "68a9c85f31f4414606e7da79",
"phone": "+78005553535",
"address": "101000, Moscow, Boulevard, 291",
"userId": "68a9c9b52eab80fa02ee6ccb"
}
Документ User.
{
"_id": "68a9c9b52eab80fa02ee6ccb",
"name": "John Doe"
}
Извлечение документа User и разрешение связи profile
.
const user = await userRep.findOne({include: 'profile'});
console.log(user);
// {
// id: '68a9c9b52eab80fa02ee6ccb',
// name: 'John Doe',
// profile: {
// id: '68a9c85f31f4414606e7da79',
// phone: '+78005553535',
// address: '101000, Moscow, Boulevard, 291',
// userId: '68a9c9b52eab80fa02ee6ccb'
// }
// }
Целевая модель ссылается на текущую по принципу один ко многим.
(обратная сторона Belongs To)
import {RelationType} from '@e22m4u/js-repository';
import {model} from '@e22m4u/js-repository-decorators';
import {relation} from '@e22m4u/js-repository-decorators';
import {property} from '@e22m4u/js-repository-decorators';
// модель Article
@model()
class Article {
@property(DataType.STRING)
title?: string;
@property(DataType.STRING)
authorId?: string;
@relation({
type: RelationType.BELONGS_TO,
model: Author.name,
// см. Belongs To
})
author?: Author;
}
// модель Author
@model()
class Author {
@property(DataType.STRING)
name?: string;
@relation({
type: RelationType.HAS_MANY, // <=
model: Article.name,
foreignKey: 'authorId',
})
articles?: Article[];
}
Коллекция Articles.
[
{
"_id": "68a9ccc43fe39dd49b4d283c",
"title": "The Bottle and the Babe",
"authorId": "68a9c9b52eab80fa02ee6ccb"
},
{
"_id": "68a9cd32f06233bba3aeadfe",
"title": "The History Logs",
"authorId": "68a9c9b52eab80fa02ee6ccb"
}
]
Документ Author.
{
"_id": "68a9c9b52eab80fa02ee6ccb",
"name": "John Doe"
}
Извлечение документа Author и разрешение связи articles
.
const author = await authorRep.findOne({include: 'articles'});
console.log(author);
// {
// id: '68a9c9b52eab80fa02ee6ccb',
// name: 'John Doe',
// articles: [
// {
// id: '68a9ccc43fe39dd49b4d283c',
// title: 'The Bottle and the Babe',
// authorId: '68a9c9b52eab80fa02ee6ccb'
// },
// {
// id: '68a9cd32f06233bba3aeadfe',
// title: 'The History Logs',
// authorId: '68a9c9b52eab80fa02ee6ccb'
// }
// ]
// }
Связь через массив идентификаторов.
import {RelationType} from '@e22m4u/js-repository';
import {model} from '@e22m4u/js-repository-decorators';
import {relation} from '@e22m4u/js-repository-decorators';
import {property} from '@e22m4u/js-repository-decorators';
// модель City
@model()
class City {
@property(DataType.STRING)
name?: string;
}
// модель User
@model()
class User {
@property(DataType.STRING)
name?: string;
@property({
type: DataType.ARRAY,
itemType: DataType.STRING,
})
cityIds?: string[];
@relation({
type: RelationType.REFERENCES_MANY,
model: City.name,
foreignKey: 'cityIds',
})
cities?: City[];
}
Коллекция Cities.
[
{
"_id": "68a9c79e0f69f169bd711d5d",
"name": "Moscow"
},
{
"_id": "68a9c839d0d046bcd43df978",
"name": "Saint Petersburg"
}
]
Документ User.
{
"_id": "68a9c85f31f4414606e7da79",
"name": "John Doe",
"cityIds": [
"68a9c79e0f69f169bd711d5d",
"68a9c839d0d046bcd43df978"
]
}
Извлечение документа User и разрешение связи cities
.
const user = await userRep.findOne({include: 'cities'});
console.log(user);
// {
// id: '68a9c85f31f4414606e7da79',
// name: 'John Doe',
// cityIds: [
// '68a9c79e0f69f169bd711d5d',
// '68a9c839d0d046bcd43df978'
// ],
// cities: [
// {
// id: '68a9c79e0f69f169bd711d5d',
// name: 'Moscow'
// },
// {
// id: '68a9c839d0d046bcd43df978',
// name: 'Saint Petersburg'
// }
// ]
// }
Текущая модель ссылается на целевую, используя внешний ключ и дискриминатор.
import {RelationType} from '@e22m4u/js-repository';
import {model} from '@e22m4u/js-repository-decorators';
import {relation} from '@e22m4u/js-repository-decorators';
import {property} from '@e22m4u/js-repository-decorators';
// модель Author
@model()
class Author {
@property(DataType.STRING)
name?: string;
@property(DataType.NUMBER)
age?: number;
}
// модель Article
@model()
class Article {
@property(DataType.STRING)
title?: string;
@property(DataType.STRING)
content?: string;
}
// модель Image
@model()
class Image {
@property(DataType.STRING)
referenceId?: string;
@property(DataType.STRING)
referenceType?: string;
@relation({
type: RelationType.BELONGS_TO, // <=
polymorphic: true, // <=
// полиморфный режим позволяет хранить название целевой модели
// в свойстве-дискриминаторе, которое формируется согласно
// названию связи с постфиксом "Type", и в данном случае
// название целевой модели хранит "referenceType",
// а идентификатор документа "referenceId"
foreignKey: 'referenceId', // (не обязательно)
discriminator: 'referenceType' // (не обязательно)
})
reference?: Author | Article;
}
Документ Author.
{
"_id": "68a9c85f31f4414606e7da78",
"name": "John Doe",
"age": 24
}
Документ Article.
{
"_id": "68a9cfd16767b49624fd16d6",
"title": "The History Logs",
"content": "First published in 1912..."
}
Коллекция Images.
[
{
"_id": "68a9c9b52eab80fa02ee6ccb",
"path": "/storage/upload/12.png",
"referenceType": "Author",
"referenceId": "68a9c85f31f4414606e7da78"
},
{
"_id": "68a9cfdf43fb7961ad68af1b",
"path": "/storage/upload/13.png",
"referenceType": "Article",
"referenceId": "68a9cfd16767b49624fd16d6"
}
]
Извлечение документов Image и разрешение связи reference
.
const images = await imageRep.find({include: 'reference'});
console.log(images);
// [
// {
// id: '68a9c9b52eab80fa02ee6ccb',
// path: '/storage/upload/12.png',
// referenceType: 'Author',
// referenceId: '68a9c85f31f4414606e7da78',
// reference: {
// id: '68a9c85f31f4414606e7da78',
// name: 'John Doe',
// age: 24
// }
// },
// {
// id: '68a9cfdf43fb7961ad68af1b',
// path: '/storage/upload/13.png',
// referenceType: 'Article',
// referenceId: '68a9cfd16767b49624fd16d6',
// reference: {
// id: '68a9cfd16767b49624fd16d6',
// title: 'The History Logs',
// content: 'First published in 1912...'
// }
// }
// ]
Целевая модель ссылается на текущую, используя внешний ключ и дискриминатор.
(обратная сторона полиморфной Belongs To)
import {RelationType} from '@e22m4u/js-repository';
import {model} from '@e22m4u/js-repository-decorators';
import {relation} from '@e22m4u/js-repository-decorators';
import {property} from '@e22m4u/js-repository-decorators';
// модель Image
@model()
class Image {
@property(DataType.STRING)
referenceId?: string;
@property(DataType.STRING)
referenceType?: string;
@relation({
type: RelationType.BELONGS_TO,
polymorphic: true,
// см. Belongs To (полиморфная версия)
foreignKey: 'referenceId', // (не обязательно)
discriminator: 'referenceType' // (не обязательно)
})
reference?: object;
}
// модель User
@model()
class User {
@property(DataType.STRING)
name?: string;
@relation({
type: RelationType.HAS_ONE,
model: Image.name, // название целевой модели
polymorphic: 'reference', // название полиморфной связи целевой модели
// вместо названия связи можно явно указать свойства целевой модели
// polymorphic: true,
// foreignKey: 'referenceId',
// discriminator: 'referenceType',
})
avatar?: Image;
}
Документ Image.
{
"_id": "68a9c9b52eab80fa02ee6ccb",
"path": "/storage/upload/14.png",
"referenceType": "User",
"referenceId": "68a9c85f31f4414606e7da78"
}
Документ User.
{
"_id": "68a9c85f31f4414606e7da78",
"name": "John Doe"
}
Извлечение документа User и разрешение связи avatar
.
const user = await userRep.findOne({include: 'avatar'});
console.log(user);
// {
// id: '68a9c85f31f4414606e7da78',
// name: 'John Doe',
// avatar: {
// id: '68a9c9b52eab80fa02ee6ccb',
// path: '/storage/upload/14.png',
// referenceType: 'User',
// referenceId: '68a9c85f31f4414606e7da78'
// }
// }
Целевая модель ссылается на текущую, используя внешний ключ и дискриминатор.
(обратная сторона полиморфной Belongs To)
import {RelationType} from '@e22m4u/js-repository';
import {model} from '@e22m4u/js-repository-decorators';
import {relation} from '@e22m4u/js-repository-decorators';
import {property} from '@e22m4u/js-repository-decorators';
// модель Image
@model()
class Image {
@property(DataType.STRING)
referenceId?: string;
@property(DataType.STRING)
referenceType?: string;
@relation({
type: RelationType.BELONGS_TO,
polymorphic: true,
// см. Belongs To (полиморфная версия)
foreignKey: 'referenceId', // (не обязательно)
discriminator: 'referenceType' // (не обязательно)
})
reference?: object;
}
// модель Gallery
@model()
class Gallery {
@property(DataType.STRING)
title?: string;
@relation({
type: RelationType.HAS_MANY,
model: Image.name, // название целевой модели
polymorphic: 'reference', // название полиморфной связи целевой модели
// вместо названия связи можно явно указать свойства целевой модели
// polymorphic: true,
// foreignKey: 'referenceId',
// discriminator: 'referenceType',
})
images?: Image[];
}
Коллекция Images.
[
{
"_id": "68aa0db2dedc4922180b9ebf",
"path": "/storage/upload/15.png",
"referenceType": "Gallery",
"referenceId": "68aa0d9bae2ef42208c2f4ec"
},
{
"_id": "68aa0db7852e06b0c3a1662b",
"path": "/storage/upload/16.png",
"referenceType": "Gallery",
"referenceId": "68aa0d9bae2ef42208c2f4ec"
}
]
Документ Gallery.
{
"_id": "68aa0d9bae2ef42208c2f4ec",
"title": "Photos of spring holidays."
}
Извлечение документа Gallery и разрешение связи images
.
const user = await userRep.findOne({include: 'images'});
console.log(user);
// {
// id: '68aa0d9bae2ef42208c2f4ec',
// title: 'Photos of spring holidays.',
// images: [
// {
// _id: '68aa0db2dedc4922180b9ebf',
// path: '/storage/upload/15.png',
// referenceType: 'Gallery',
// referenceId: '68aa0d9bae2ef42208c2f4ec'
// },
// {
// id: '68aa0db7852e06b0c3a1662b',
// path: '/storage/upload/16.png',
// referenceType: 'Gallery',
// referenceId: '68aa0d9bae2ef42208c2f4ec'
// }
// ]
// }
npm run test
MIT