chore: Bring typeORM fork to monorepo (#32898)

This commit is contained in:
Matsu
2026-06-29 21:53:20 +03:00
committed by GitHub
parent 1a00b07526
commit ec48b83bed
2579 changed files with 173785 additions and 99 deletions
+69 -1
View File
@@ -1,7 +1,7 @@
{
"version": 1,
"generated": "2026-06-04T06:55:45.363Z",
"totalViolations": 148,
"totalViolations": 159,
"violations": {
"packages/@n8n/ai-workflow-builder.ee/package.json": [
{
@@ -1058,6 +1058,74 @@
"message": "Override \"langsmith\" duplicates a catalog entry for langsmith — set the override to \"catalog:\" or remove it",
"hash": "170e854b445c"
}
],
"packages/@n8n/typeorm/package.json": [
{
"rule": "catalog-violations",
"line": 118,
"message": "reflect-metadata@^0.2.2 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "558f12c2dd9c"
},
{
"rule": "catalog-violations",
"line": 122,
"message": "uuid@^9.0.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "0304587b7acc"
},
{
"rule": "catalog-violations",
"line": 67,
"message": "@types/node@^18.13.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "0df18a02f2d0"
},
{
"rule": "catalog-violations",
"line": 72,
"message": "@types/uuid@^9.0.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "0bcbecb362c9"
},
{
"rule": "catalog-violations",
"line": 76,
"message": "eslint@^8.44.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "0036144a407d"
},
{
"rule": "catalog-violations",
"line": 78,
"message": "pg@^8.17.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "6cac348bdcc8"
},
{
"rule": "catalog-violations",
"line": 80,
"message": "prettier@^2.8.3 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "869ee9ce64bc"
},
{
"rule": "catalog-violations",
"line": 81,
"message": "rimraf@^4.1.2 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "48f117898c57"
},
{
"rule": "catalog-violations",
"line": 84,
"message": "source-map-support@^0.5.21 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "c27bcf7b8b06"
},
{
"rule": "catalog-violations",
"line": 87,
"message": "typescript@^5.3.3 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "050d5b2d74e1"
},
{
"rule": "catalog-violations",
"line": 90,
"message": "pg@^8.17.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
"hash": "6cac348bdcc8"
}
]
}
}
@@ -28,7 +28,7 @@
"@n8n/db": "workspace:*",
"@n8n/di": "workspace:*",
"@n8n/permissions": "workspace:*",
"@n8n/typeorm": "catalog:",
"@n8n/typeorm": "workspace:*",
"n8n-workflow": "workspace:*",
"reflect-metadata": "catalog:",
"uuid": "catalog:",
+1 -1
View File
@@ -38,7 +38,7 @@
"@n8n/di": "workspace:*",
"@n8n/permissions": "workspace:*",
"@n8n/utils": "workspace:*",
"@n8n/typeorm": "catalog:",
"@n8n/typeorm": "workspace:*",
"class-validator": "0.14.0",
"flatted": "catalog:",
"lodash": "catalog:",
+20
View File
@@ -0,0 +1,20 @@
.nyc_output/
.DS_Store
.vscode/
.idea/
build/
dist/
coverage/
node_modules/
/ormconfig.json
ormlogs.log
npm-debug.log
/test/github-issues/799/tmp/*
# test projects generated by test/github-issues/issue-8975.ts
/*TestProject
# ignore yarn2 artifacts but allow yarn.lock (forces yarn1 compat which is node_modules)
.yarn/
.yarn*
*.iml
temp/**/*.db*
temp/test
+21
View File
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2015-2024 TypeORM. http://typeorm.github.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because it is too large Load Diff
+155
View File
@@ -0,0 +1,155 @@
{
"name": "@n8n/typeorm",
"version": "0.3.20-18",
"description": "Data-Mapper ORM for TypeScript, ES7, ES6, ES5. Supports PostgreSQL and SQLite databases.",
"license": "MIT",
"readmeFilename": "README.md",
"author": {
"name": "Umed Khudoiberdiev",
"email": "[email protected]"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/index.js",
"import": "./dist/index.js"
},
"./*.js": "./dist/*.js",
"./*": {
"types": "./dist/*.d.ts",
"require": "./dist/*.js",
"import": "./dist/*.js"
}
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"typesVersions": {
"*": {
"dist/*": [
"./dist/*"
],
"*": [
"./dist/*"
]
}
},
"files": [
"dist/**/*"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/typeorm/typeorm.git"
},
"bugs": {
"url": "https://github.com/typeorm/typeorm/issues"
},
"homepage": "https://typeorm.io",
"tags": [
"orm",
"typescript",
"typescript-orm",
"postgresql",
"postgresql-orm",
"sqlite",
"sqlite-orm"
],
"devDependencies": {
"@tsconfig/node16": "^16.1.1",
"@types/app-root-path": "^1.2.4",
"@types/chai": "^4.3.4",
"@types/chai-as-promised": "^7.1.5",
"@types/debug": "^4.1.12",
"@types/mkdirp": "^1.0.2",
"@types/mocha": "^10.0.1",
"@types/node": "^18.13.0",
"@types/pg": "^8.15.6",
"@types/sha.js": "^2.4.0",
"@types/sinon": "^10.0.13",
"@types/source-map-support": "^0.5.6",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^8.35.0",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"eslint": "^8.44.0",
"mocha": "^10.2.0",
"pg": "^8.17.0",
"pg-query-stream": "^4.10.3",
"prettier": "^2.8.3",
"rimraf": "^4.1.2",
"sinon": "^15.0.1",
"sinon-chai": "^3.7.0",
"source-map-support": "^0.5.21",
"sqlite3": "5.1.7",
"ts-node": "^10.9.1",
"typescript": "^5.3.3"
},
"peerDependencies": {
"pg": "^8.17.0",
"pg-native": "^3.5.2",
"pg-query-stream": "^4.10.3",
"sqlite3": "5.1.7"
},
"peerDependenciesMeta": {
"pg": {
"optional": true
},
"pg-native": {
"optional": true
},
"pg-query-stream": {
"optional": true
},
"sqlite3": {
"optional": true
}
},
"dependencies": {
"app-root-path": "^3.1.0",
"async-mutex": "^0.5.0",
"chalk": "^4.1.2",
"dayjs": "^1.11.9",
"debug": "^4.3.4",
"dotenv": "catalog:",
"glob": "^10.0.0",
"mkdirp": "^2.1.3",
"reflect-metadata": "^0.2.2",
"sha.js": "^2.4.12",
"tarn": "3.0.2",
"tslib": "^2.5.0",
"uuid": "^9.0.0"
},
"scripts": {
"test": "tsc && mocha --file ./build/compiled/test/utils/test-setup.js --recursive --timeout 90000 ./build/compiled/test",
"test:unit": "echo \"No unit tests for @n8n/typeorm (vendored)\" && exit 0",
"test-fast": "mocha --file ./build/compiled/test/utils/test-setup.js --bail --recursive --timeout 90000 ./build/compiled/test",
"typecheck": "tsc --noEmit",
"build": "pnpm compile",
"build:unchecked": "tsc -p tsconfig.build.json --noCheck",
"compile": "tsc -p tsconfig.build.json",
"watch": "tsc -w -p tsconfig.build.json"
},
"funding": "https://opencollective.com/typeorm",
"collective": {
"type": "opencollective",
"url": "https://opencollective.com/typeorm",
"logo": "https://opencollective.com/opencollective/logo.txt"
},
"nyc": {
"all": true,
"cache": false,
"exclude": [
"**/*.d.ts"
],
"extension": [
".ts"
],
"include": [
"build/compiled/src/**",
"src/**"
],
"reporter": "json"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,19 @@
import { Column, Entity } from '../../../src/index';
import { PrimaryColumn } from '../../../src/decorator/columns/PrimaryColumn';
import { Generated } from '../../../src/decorator/Generated';
@Entity('sample01_post')
export class Post {
@PrimaryColumn()
@Generated()
id: number;
@Column()
title: string;
@Column()
text: string;
@Column({ nullable: false })
likesCount: number;
}
@@ -0,0 +1,79 @@
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
import { PostDetails } from './PostDetails';
import { PostCategory } from './PostCategory';
import { PostAuthor } from './PostAuthor';
import { PostInformation } from './PostInformation';
import { PostImage } from './PostImage';
import { PostMetadata } from './PostMetadata';
import { JoinColumn } from '../../../src/decorator/relations/JoinColumn';
@Entity('sample2_post')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
// post has relation with category, however inverse relation is not set (category does not have relation with post set)
@OneToOne((type) => PostCategory, {
cascade: true,
})
@JoinColumn()
category: PostCategory;
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
// relation it will be inserted automatically to the db when you save this Post entity
@OneToOne(
(type) => PostDetails,
(details) => details.post,
{
cascade: ['insert'],
},
)
@JoinColumn()
details?: PostDetails;
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@OneToOne(
(type) => PostImage,
(image) => image.post,
{
cascade: ['update'],
},
)
@JoinColumn()
image: PostImage;
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@OneToOne(
(type) => PostMetadata,
(metadata) => metadata.post,
)
@JoinColumn()
metadata: PostMetadata | null;
// post has relation with details. full cascades here
@OneToOne(
(type) => PostInformation,
(information) => information.post,
{
cascade: true,
},
)
@JoinColumn()
information: PostInformation;
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
@OneToOne(
(type) => PostAuthor,
(author) => author.post,
)
@JoinColumn()
author: PostAuthor;
}
@@ -0,0 +1,17 @@
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample2_post_author')
export class PostAuthor {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToOne(
(type) => Post,
(post) => post.author,
)
post: Post;
}
@@ -0,0 +1,10 @@
import { Column, Entity, PrimaryGeneratedColumn } from '../../../src/index';
@Entity('sample2_post_category')
export class PostCategory {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
@@ -0,0 +1,26 @@
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample2_post_details')
export class PostDetails {
@PrimaryGeneratedColumn()
id: number;
@Column()
authorName: string;
@Column()
comment: string;
@Column()
metadata: string;
@OneToOne(
(type) => Post,
(post) => post.details,
{
cascade: true,
},
)
post: Post;
}
@@ -0,0 +1,17 @@
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample2_post_image')
export class PostImage {
@PrimaryGeneratedColumn()
id: number;
@Column()
url: string;
@OneToOne(
(type) => Post,
(post) => post.image,
)
post: Post;
}
@@ -0,0 +1,20 @@
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample2_post_information')
export class PostInformation {
@PrimaryGeneratedColumn()
id: number;
@Column()
text: string;
@OneToOne(
(type) => Post,
(post) => post.information,
{
cascade: ['update'],
},
)
post: Post;
}
@@ -0,0 +1,17 @@
import { Column, Entity, OneToOne, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample2_post_metadata')
export class PostMetadata {
@PrimaryGeneratedColumn()
id: number;
@Column()
description: string;
@OneToOne(
(type) => Post,
(post) => post.metadata,
)
post: Post;
}
@@ -0,0 +1,72 @@
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from '../../../src/index';
import { PostDetails } from './PostDetails';
import { PostCategory } from './PostCategory';
import { PostAuthor } from './PostAuthor';
import { PostInformation } from './PostInformation';
import { PostImage } from './PostImage';
import { PostMetadata } from './PostMetadata';
@Entity('sample3_post')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
// post has relation with category, however inverse relation is not set (category does not have relation with post set)
@ManyToOne((type) => PostCategory, {
cascade: true,
})
category: PostCategory;
// post has relation with details. cascade inserts here means if new PostDetails instance will be set to this
// relation it will be inserted automatically to the db when you save this Post entity
@ManyToOne(
(type) => PostDetails,
(details) => details.posts,
{
cascade: ['insert'],
},
)
details?: PostDetails;
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@ManyToOne(
(type) => PostImage,
(image) => image.posts,
{
cascade: ['update'],
},
)
image: PostImage;
// post has relation with details. cascade update here means if new PostDetail instance will be set to this relation
// it will be inserted automatically to the db when you save this Post entity
@ManyToOne(
(type) => PostMetadata,
(metadata) => metadata.posts,
)
metadata: PostMetadata | null;
// post has relation with details. full cascades here
@ManyToOne(
(type) => PostInformation,
(information) => information.posts,
{
cascade: true,
},
)
information: PostInformation;
// post has relation with details. not cascades here. means cannot be persisted, updated or removed
@ManyToOne(
(type) => PostAuthor,
(author) => author.posts,
)
author: PostAuthor;
}
@@ -0,0 +1,17 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample3_post_author')
export class PostAuthor {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@OneToMany(
(type) => Post,
(post) => post.author,
)
posts: Post[];
}
@@ -0,0 +1,10 @@
import { Column, Entity, PrimaryGeneratedColumn } from '../../../src/index';
@Entity('sample3_post_category')
export class PostCategory {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
@@ -0,0 +1,35 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample3_post_details')
export class PostDetails {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: String,
nullable: true,
})
authorName: string | null;
@Column({
type: String,
nullable: true,
})
comment: string | null;
@Column({
type: String,
nullable: true,
})
metadata: string | null;
@OneToMany(
(type) => Post,
(post) => post.details,
{
cascade: true,
},
)
posts: Post[];
}
@@ -0,0 +1,17 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample3_post_image')
export class PostImage {
@PrimaryGeneratedColumn()
id: number;
@Column()
url: string;
@OneToMany(
(type) => Post,
(post) => post.image,
)
posts: Post[];
}
@@ -0,0 +1,20 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample3_post_information')
export class PostInformation {
@PrimaryGeneratedColumn()
id: number;
@Column()
text: string;
@OneToMany(
(type) => Post,
(post) => post.information,
{
cascade: ['update'],
},
)
posts: Post[];
}
@@ -0,0 +1,17 @@
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample3_post_metadata')
export class PostMetadata {
@PrimaryGeneratedColumn()
id: number;
@Column()
description: string;
@OneToMany(
(type) => Post,
(post) => post.metadata,
)
posts: Post[];
}
@@ -0,0 +1,81 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { PostDetails } from './PostDetails';
import { PostCategory } from './PostCategory';
import { PostAuthor } from './PostAuthor';
import { PostInformation } from './PostInformation';
import { PostImage } from './PostImage';
import { PostMetadata } from './PostMetadata';
import { JoinTable } from '../../../src/decorator/relations/JoinTable';
@Entity('sample4_post')
export class Post {
@PrimaryGeneratedColumn()
id: number;
@Column()
title: string;
@Column()
text: string;
// Post has relation with PostCategory, however inverse relation is not set
// (category does not have relation with post set)
@ManyToMany((type) => PostCategory, {
cascade: true,
})
@JoinTable()
categories: PostCategory[];
// Post has relation with PostDetails. Cascade insert here means if there is a new PostDetails instance set
// on this relation, it will be inserted automatically to the db when you save this Post entity
@ManyToMany(
(type) => PostDetails,
(details) => details.posts,
{
cascade: ['insert'],
},
)
@JoinTable()
details: PostDetails[];
// Post has relation with PostImage. Cascade update here means if there are changes to an existing PostImage, it
// will be updated automatically to the db when you save this Post entity
@ManyToMany(
(type) => PostImage,
(image) => image.posts,
{
cascade: ['update'],
},
)
@JoinTable()
images: PostImage[];
// Post has relation with PostMetadata. No cascades here means that when saving a Post entity, there will be
// no creating/updating/destroying PostMetadata.
@ManyToMany(
(type) => PostMetadata,
(metadata) => metadata.posts,
)
@JoinTable()
metadatas: PostMetadata[];
// Post has relation with PostInformation. Full cascades here
@ManyToMany(
(type) => PostInformation,
(information) => information.posts,
{
cascade: true,
},
)
@JoinTable()
informations: PostInformation[];
// Post has relation with author. No cascades here means that when saving a Post entity, there will be
// no creating/updating/destroying PostAuthor.
@ManyToMany(
(type) => PostAuthor,
(author) => author.posts,
)
@JoinTable()
authors: PostAuthor[];
}
@@ -0,0 +1,17 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample4_post_author')
export class PostAuthor {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@ManyToMany(
(type) => Post,
(post) => post.authors,
)
posts: Post[];
}
@@ -0,0 +1,10 @@
import { Column, Entity, PrimaryGeneratedColumn } from '../../../src/index';
@Entity('sample4_post_category')
export class PostCategory {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
@@ -0,0 +1,35 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample4_post_details')
export class PostDetails {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: String,
nullable: true,
})
authorName: string | null;
@Column({
type: String,
nullable: true,
})
comment: string | null;
@Column({
type: String,
nullable: true,
})
metadata: string | null;
@ManyToMany(
(type) => Post,
(post) => post.details,
{
cascade: true,
},
)
posts: Post[];
}
@@ -0,0 +1,17 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample4_post_image')
export class PostImage {
@PrimaryGeneratedColumn()
id: number;
@Column()
url: string;
@ManyToMany(
(type) => Post,
(post) => post.images,
)
posts: Post[];
}
@@ -0,0 +1,20 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample4_post_information')
export class PostInformation {
@PrimaryGeneratedColumn()
id: number;
@Column()
text: string;
@ManyToMany(
(type) => Post,
(post) => post.informations,
{
cascade: ['update'],
},
)
posts: Post[];
}
@@ -0,0 +1,17 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from '../../../src/index';
import { Post } from './Post';
@Entity('sample4_post_metadata')
export class PostMetadata {
@PrimaryGeneratedColumn()
id: number;
@Column()
description: string;
@ManyToMany(
(type) => Post,
(post) => post.metadatas,
)
posts: Post[];
}
+270
View File
@@ -0,0 +1,270 @@
import { ObjectLiteral } from '../common/ObjectLiteral';
import { DataSource } from '../data-source/DataSource';
import { QueryRunner } from '../query-runner/QueryRunner';
import { Table } from '../schema-builder/table/Table';
import { QueryResultCache } from './QueryResultCache';
import { QueryResultCacheOptions } from './QueryResultCacheOptions';
/**
* Caches query result into current database, into separate table called "query-result-cache".
*/
export class DbQueryResultCache implements QueryResultCache {
// -------------------------------------------------------------------------
// Private properties
// -------------------------------------------------------------------------
private queryResultCacheTable: string;
private queryResultCacheDatabase?: string;
private queryResultCacheSchema?: string;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(protected connection: DataSource) {
const { schema } = this.connection.driver.options as any;
const database = this.connection.driver.database;
const cacheOptions =
typeof this.connection.options.cache === 'object' ? this.connection.options.cache : {};
const cacheTableName = cacheOptions.tableName || 'query-result-cache';
this.queryResultCacheDatabase = database;
this.queryResultCacheSchema = schema;
this.queryResultCacheTable = this.connection.driver.buildTableName(
cacheTableName,
schema,
database,
);
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Creates a connection with given cache provider.
*/
async connect(): Promise<void> {}
/**
* Disconnects with given cache provider.
*/
async disconnect(): Promise<void> {}
/**
* Creates table for storing cache if it does not exist yet.
*/
async synchronize(queryRunner?: QueryRunner): Promise<void> {
queryRunner = this.getQueryRunner(queryRunner);
const driver = this.connection.driver;
const tableExist = await queryRunner.hasTable(this.queryResultCacheTable); // todo: table name should be configurable
if (tableExist) return;
await queryRunner.createTable(
new Table({
database: this.queryResultCacheDatabase,
schema: this.queryResultCacheSchema,
name: this.queryResultCacheTable,
columns: [
{
name: 'id',
isPrimary: true,
isNullable: false,
type: driver.normalizeType({
type: driver.mappedDataTypes.cacheId,
}),
generationStrategy: 'increment',
isGenerated: true,
},
{
name: 'identifier',
type: driver.normalizeType({
type: driver.mappedDataTypes.cacheIdentifier,
}),
isNullable: true,
},
{
name: 'time',
type: driver.normalizeType({
type: driver.mappedDataTypes.cacheTime,
}),
isPrimary: false,
isNullable: false,
},
{
name: 'duration',
type: driver.normalizeType({
type: driver.mappedDataTypes.cacheDuration,
}),
isPrimary: false,
isNullable: false,
},
{
name: 'query',
type: driver.normalizeType({
type: driver.mappedDataTypes.cacheQuery,
}),
isPrimary: false,
isNullable: false,
},
{
name: 'result',
type: driver.normalizeType({
type: driver.mappedDataTypes.cacheResult,
}),
isNullable: false,
},
],
}),
);
}
/**
* Get data from cache.
* Returns cache result if found.
* Returns undefined if result is not cached.
*/
getFromCache(
options: QueryResultCacheOptions,
queryRunner?: QueryRunner,
): Promise<QueryResultCacheOptions | undefined> {
queryRunner = this.getQueryRunner(queryRunner);
const qb = this.connection
.createQueryBuilder(queryRunner)
.select()
.from(this.queryResultCacheTable, 'cache');
if (options.identifier) {
return qb
.where(`${qb.escape('cache')}.${qb.escape('identifier')} = :identifier`)
.setParameters({
identifier: options.identifier,
})
.cache(false) // disable cache to avoid infinite loops when cache is alwaysEnable
.getRawOne();
} else if (options.query) {
return qb
.where(`${qb.escape('cache')}.${qb.escape('query')} = :query`)
.setParameters({
query: options.query,
})
.cache(false) // disable cache to avoid infinite loops when cache is alwaysEnable
.getRawOne();
}
return Promise.resolve(undefined);
}
/**
* Checks if cache is expired or not.
*/
isExpired(savedCache: QueryResultCacheOptions): boolean {
const duration =
typeof savedCache.duration === 'string' ? parseInt(savedCache.duration) : savedCache.duration;
return (
(typeof savedCache.time === 'string' ? parseInt(savedCache.time as any) : savedCache.time)! +
duration <
new Date().getTime()
);
}
/**
* Stores given query result in the cache.
*/
async storeInCache(
options: QueryResultCacheOptions,
savedCache: QueryResultCacheOptions | undefined,
queryRunner?: QueryRunner,
): Promise<void> {
const shouldCreateQueryRunner =
queryRunner === undefined || queryRunner?.getReplicationMode() === 'slave';
if (queryRunner === undefined || shouldCreateQueryRunner) {
queryRunner = this.connection.createQueryRunner('master');
}
let insertedValues: ObjectLiteral = options;
if (savedCache && savedCache.identifier) {
// if exist then update
const qb = queryRunner.manager
.createQueryBuilder()
.update(this.queryResultCacheTable)
.set(insertedValues);
qb.where(`${qb.escape('identifier')} = :condition`, {
condition: insertedValues.identifier,
});
await qb.execute();
} else if (savedCache && savedCache.query) {
// if exist then update
const qb = queryRunner.manager
.createQueryBuilder()
.update(this.queryResultCacheTable)
.set(insertedValues);
qb.where(`${qb.escape('query')} = :condition`, {
condition: insertedValues.query,
});
await qb.execute();
} else {
// otherwise insert
await queryRunner.manager
.createQueryBuilder()
.insert()
.into(this.queryResultCacheTable)
.values(insertedValues)
.execute();
}
if (shouldCreateQueryRunner) {
await queryRunner.release();
}
}
/**
* Clears everything stored in the cache.
*/
async clear(queryRunner: QueryRunner): Promise<void> {
return this.getQueryRunner(queryRunner).clearTable(this.queryResultCacheTable);
}
/**
* Removes all cached results by given identifiers from cache.
*/
async remove(identifiers: string[], queryRunner?: QueryRunner): Promise<void> {
let _queryRunner: QueryRunner = queryRunner || this.getQueryRunner();
await Promise.all(
identifiers.map((identifier) => {
const qb = _queryRunner.manager.createQueryBuilder();
return qb
.delete()
.from(this.queryResultCacheTable)
.where(`${qb.escape('identifier')} = :identifier`, {
identifier,
})
.execute();
}),
);
if (!queryRunner) {
await _queryRunner.release();
}
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Gets a query runner to work with.
*/
protected getQueryRunner(queryRunner?: QueryRunner): QueryRunner {
if (queryRunner) return queryRunner;
return this.connection.createQueryRunner();
}
}
+54
View File
@@ -0,0 +1,54 @@
import { QueryResultCacheOptions } from './QueryResultCacheOptions';
import { QueryRunner } from '../query-runner/QueryRunner';
/**
* Implementations of this interface provide different strategies to cache query builder results.
*/
export interface QueryResultCache {
/**
* Creates a connection with given cache provider.
*/
connect(): Promise<void>;
/**
* Closes a connection with given cache provider.
*/
disconnect(): Promise<void>;
/**
* Performs operations needs to be created during schema synchronization.
*/
synchronize(queryRunner?: QueryRunner): Promise<void>;
/**
* Caches given query result.
*/
getFromCache(
options: QueryResultCacheOptions,
queryRunner?: QueryRunner,
): Promise<QueryResultCacheOptions | undefined>;
/**
* Stores given query result in the cache.
*/
storeInCache(
options: QueryResultCacheOptions,
savedCache: QueryResultCacheOptions | undefined,
queryRunner?: QueryRunner,
): Promise<void>;
/**
* Checks if cache is expired or not.
*/
isExpired(savedCache: QueryResultCacheOptions): boolean;
/**
* Clears everything stored in the cache.
*/
clear(queryRunner?: QueryRunner): Promise<void>;
/**
* Removes all cached results by given identifiers from cache.
*/
remove(identifiers: string[], queryRunner?: QueryRunner): Promise<void>;
}
@@ -0,0 +1,37 @@
import { DbQueryResultCache } from './DbQueryResultCache';
import { QueryResultCache } from './QueryResultCache';
import { DataSource } from '../data-source/DataSource';
import { TypeORMError } from '../error/TypeORMError';
/**
* Caches query result into the database.
*/
export class QueryResultCacheFactory {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(protected connection: DataSource) {}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Creates a new query result cache based on connection options.
*/
create(): QueryResultCache {
if (!this.connection.options.cache)
throw new TypeORMError(
`To use cache you need to enable it in connection options by setting cache: true or providing some caching options. Example: { host: ..., username: ..., cache: true }`,
);
const cache: any = this.connection.options.cache;
if (cache.provider && typeof cache.provider === 'function') {
return cache.provider(this.connection);
}
return new DbQueryResultCache(this.connection);
}
}
@@ -0,0 +1,30 @@
/**
* Options passed to QueryResultCache class.
*/
export interface QueryResultCacheOptions {
/**
* Cache identifier set by user.
* Can be empty.
*/
identifier?: string;
/**
* Time, when cache was created.
*/
time?: number;
/**
* Duration in milliseconds during which results will be returned from cache.
*/
duration: number;
/**
* Cached query.
*/
query?: string;
/**
* Query result that will be cached.
*/
result?: any;
}
@@ -0,0 +1,14 @@
/**
* Same as Partial<T> but goes deeper and makes Partial<T> all its properties and sub-properties.
*/
export type DeepPartial<T> =
| T
| (T extends Array<infer U>
? DeepPartial<U>[]
: T extends Map<infer K, infer V>
? Map<DeepPartial<K>, DeepPartial<V>>
: T extends Set<infer M>
? Set<DeepPartial<M>>
: T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T);
@@ -0,0 +1,11 @@
import { ObjectType } from './ObjectType';
import { EntitySchema } from '..';
/**
* Entity target.
*/
export type EntityTarget<Entity> =
| ObjectType<Entity>
| EntitySchema<Entity>
| string
| { type: Entity; name: string };
@@ -0,0 +1,6 @@
/**
* List of T-s passed as an array or object map.
*
* Example usage: entities as an array of imported using import * as syntax.
*/
export type MixedList<T> = T[] | { [key: string]: T };
@@ -0,0 +1,6 @@
/**
* Interface of the simple literal object with any string keys.
*/
export interface ObjectLiteral {
[key: string]: any;
}
@@ -0,0 +1,4 @@
/**
* Represents some Type of the Object.
*/
export type ObjectType<T> = { new (): T } | Function;
@@ -0,0 +1,7 @@
/**
* Pick only the keys that match the Type `U`
*/
export type PickKeysByType<T, U> = string &
keyof {
[P in keyof T as T[P] extends U ? P : never]: T[P];
};
@@ -0,0 +1,14 @@
/**
* Wrapper type for relation type definitions in entities.
* Used to circumvent ESM modules circular dependency issue caused by reflection metadata saving the type of the property.
*
* Usage example:
* @Entity()
* export default class User {
*
* @OneToOne(() => Profile, profile => profile.user)
* profile: Relation<Profile>;
*
* }
*/
export type Relation<T> = T;
@@ -0,0 +1,10 @@
import { DataSource } from '../data-source/DataSource';
/**
* Connection is a single database ORM connection to a specific database.
* Its not required to be a database connection, depend on database type it can create connection pool.
* You can have multiple connections to multiple databases in your application.
*
* @deprecated
*/
export class Connection extends DataSource {}
@@ -0,0 +1,66 @@
import { DataSource } from '../data-source/DataSource';
import { ConnectionNotFoundError } from '../error/ConnectionNotFoundError';
import { DataSourceOptions } from '../data-source/DataSourceOptions';
import { AlreadyHasActiveConnectionError } from '../error/AlreadyHasActiveConnectionError';
/**
* ConnectionManager is used to store and manage multiple orm connections.
* It also provides useful factory methods to simplify connection creation.
*
* @deprecated
*/
export class ConnectionManager {
/**
* List of connections registered in this connection manager.
*/
get connections(): DataSource[] {
return Array.from(this.connectionMap.values());
}
/**
* Internal lookup to quickly get from a connection name to the Connection object.
*/
private readonly connectionMap: Map<string, DataSource> = new Map();
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Checks if connection with the given name exist in the manager.
*/
has(name: string): boolean {
return this.connectionMap.has(name);
}
/**
* Gets registered connection with the given name.
* If connection name is not given then it will get a default connection.
* Throws error if connection with the given name was not found.
*/
get(name: string = 'default'): DataSource {
const connection = this.connectionMap.get(name);
if (!connection) throw new ConnectionNotFoundError(name);
return connection;
}
/**
* Creates a new connection based on the given connection options and registers it in the manager.
* Connection won't be established, you'll need to manually call connect method to establish connection.
*/
create(options: DataSourceOptions): DataSource {
// check if such connection is already registered
const existConnection = this.connectionMap.get(options.name || 'default');
if (existConnection) {
// if connection is registered and its not closed then throw an error
if (existConnection.isInitialized)
throw new AlreadyHasActiveConnectionError(options.name || 'default');
}
// create a new connection
const connection = new DataSource(options);
this.connectionMap.set(connection.name, connection);
return connection;
}
}
@@ -0,0 +1,101 @@
import { importClassesFromDirectories } from '../util/DirectoryExportedClassesLoader';
import { OrmUtils } from '../util/OrmUtils';
import { getFromContainer } from '../container';
import { MigrationInterface } from '../migration/MigrationInterface';
import { getMetadataArgsStorage } from '../globals';
import { EntityMetadataBuilder } from '../metadata-builder/EntityMetadataBuilder';
import { EntitySchemaTransformer } from '../entity-schema/EntitySchemaTransformer';
import { DataSource } from '../data-source/DataSource';
import { EntitySchema } from '../entity-schema/EntitySchema';
import { EntityMetadata } from '../metadata/EntityMetadata';
import { EntitySubscriberInterface } from '../subscriber/EntitySubscriberInterface';
import { InstanceChecker } from '../util/InstanceChecker';
/**
* Builds migration instances, subscriber instances and entity metadatas for the given classes.
*/
export class ConnectionMetadataBuilder {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(protected connection: DataSource) {}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Builds migration instances for the given classes or directories.
*/
async buildMigrations(migrations: (Function | string)[]): Promise<MigrationInterface[]> {
const [migrationClasses, migrationDirectories] = OrmUtils.splitClassesAndStrings(migrations);
const allMigrationClasses = [
...migrationClasses,
...(await importClassesFromDirectories(this.connection.logger, migrationDirectories)),
];
return allMigrationClasses.map((migrationClass) =>
getFromContainer<MigrationInterface>(migrationClass),
);
}
/**
* Builds subscriber instances for the given classes or directories.
*/
async buildSubscribers(
subscribers: (Function | string)[],
): Promise<EntitySubscriberInterface<any>[]> {
const [subscriberClasses, subscriberDirectories] = OrmUtils.splitClassesAndStrings(
subscribers || [],
);
const allSubscriberClasses = [
...subscriberClasses,
...(await importClassesFromDirectories(this.connection.logger, subscriberDirectories)),
];
return getMetadataArgsStorage()
.filterSubscribers(allSubscriberClasses)
.map((metadata) => getFromContainer<EntitySubscriberInterface<any>>(metadata.target));
}
/**
* Builds entity metadatas for the given classes or directories.
*/
async buildEntityMetadatas(
entities: (Function | EntitySchema<any> | string)[],
): Promise<EntityMetadata[]> {
// todo: instead we need to merge multiple metadata args storages
const [entityClassesOrSchemas, entityDirectories] = OrmUtils.splitClassesAndStrings(
entities || [],
);
const entityClasses: Function[] = entityClassesOrSchemas.filter(
(entityClass) => !InstanceChecker.isEntitySchema(entityClass),
) as any;
const entitySchemas: EntitySchema<any>[] = entityClassesOrSchemas.filter((entityClass) =>
InstanceChecker.isEntitySchema(entityClass),
) as any;
const allEntityClasses = [
...entityClasses,
...(await importClassesFromDirectories(this.connection.logger, entityDirectories)),
];
allEntityClasses.forEach((entityClass) => {
// if we have entity schemas loaded from directories
if (InstanceChecker.isEntitySchema(entityClass)) {
entitySchemas.push(entityClass);
}
});
const decoratorEntityMetadatas = new EntityMetadataBuilder(
this.connection,
getMetadataArgsStorage(),
).build(allEntityClasses);
const metadataArgsStorageFromSchema = new EntitySchemaTransformer().transform(entitySchemas);
const schemaEntityMetadatas = new EntityMetadataBuilder(
this.connection,
metadataArgsStorageFromSchema,
).build();
return [...decoratorEntityMetadatas, ...schemaEntityMetadatas];
}
}
@@ -0,0 +1,10 @@
import { DataSourceOptions } from '../data-source/DataSourceOptions';
/**
* ConnectionOptions is an interface with settings and options for specific connection.
* Options contain database and other connection-related settings.
* Consumer must provide connection options for each of your connections.
*
* @deprecated use DataSourceOptions instead
*/
export type ConnectionOptions = DataSourceOptions;
@@ -0,0 +1,233 @@
import appRootPath from 'app-root-path';
import path from 'path';
import { DataSourceOptions } from '../data-source/DataSourceOptions';
import { PlatformTools } from '../platform/PlatformTools';
import { ConnectionOptionsEnvReader } from './options-reader/ConnectionOptionsEnvReader';
import { TypeORMError } from '../error';
import { isAbsolute } from '../util/PathUtils';
import { importOrRequireFile } from '../util/ImportUtils';
/**
* Reads connection options from the ormconfig.
*/
export class ConnectionOptionsReader {
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(
protected options?: {
/**
* Directory where ormconfig should be read from.
* By default its your application root (where your app package.json is located).
*/
root?: string;
/**
* Filename of the ormconfig configuration. By default its equal to "ormconfig".
*/
configName?: string;
},
) {}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Returns all connection options read from the ormconfig.
*/
async all(): Promise<DataSourceOptions[]> {
const options = await this.load();
if (!options)
throw new TypeORMError(`No connection options were found in any orm configuration files.`);
return options;
}
/**
* Gets a connection with a given name read from ormconfig.
* If connection with such name would not be found then it throw error.
*/
async get(name: string): Promise<DataSourceOptions> {
const allOptions = await this.all();
const targetOptions = allOptions.find(
(options) => options.name === name || (name === 'default' && !options.name),
);
if (!targetOptions)
throw new TypeORMError(
`Cannot find connection ${name} because its not defined in any orm configuration files.`,
);
return targetOptions;
}
/**
* Checks if there is a TypeORM configuration file.
*/
async has(name: string): Promise<boolean> {
const allOptions = await this.load();
if (!allOptions) return false;
const targetOptions = allOptions.find(
(options) => options.name === name || (name === 'default' && !options.name),
);
return !!targetOptions;
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Loads all connection options from a configuration file.
*
* todo: get in count NODE_ENV somehow
*/
protected async load(): Promise<DataSourceOptions[] | undefined> {
let connectionOptions: DataSourceOptions | DataSourceOptions[] | undefined = undefined;
const fileFormats = ['env', 'js', 'mjs', 'cjs', 'ts', 'mts', 'cts', 'json'];
// Detect if baseFilePath contains file extension
const possibleExtension = this.baseFilePath.substr(this.baseFilePath.lastIndexOf('.'));
const fileExtension = fileFormats.find((extension) => `.${extension}` === possibleExtension);
// try to find any of following configuration formats
const foundFileFormat =
fileExtension ||
fileFormats.find((format) => {
return PlatformTools.fileExist(this.baseFilePath + '.' + format);
});
// Determine config file name
const configFile = fileExtension
? this.baseFilePath
: this.baseFilePath + '.' + foundFileFormat;
// if .env file found then load all its variables into process.env using dotenv package
if (foundFileFormat === 'env') {
PlatformTools.dotenv(configFile);
} else if (PlatformTools.fileExist(this.baseDirectory + '/.env')) {
PlatformTools.dotenv(this.baseDirectory + '/.env');
}
// try to find connection options from any of available sources of configuration
if (
PlatformTools.getEnvVariable('TYPEORM_CONNECTION') ||
PlatformTools.getEnvVariable('TYPEORM_URL')
) {
connectionOptions = await new ConnectionOptionsEnvReader().read();
} else if (
foundFileFormat === 'js' ||
foundFileFormat === 'mjs' ||
foundFileFormat === 'cjs' ||
foundFileFormat === 'ts' ||
foundFileFormat === 'mts' ||
foundFileFormat === 'cts'
) {
const [importOrRequireResult, moduleSystem] = await importOrRequireFile(configFile);
const configModule = await importOrRequireResult;
if (
moduleSystem === 'esm' ||
(configModule && '__esModule' in configModule && 'default' in configModule)
) {
connectionOptions = configModule.default;
} else {
connectionOptions = configModule;
}
} else if (foundFileFormat === 'json') {
connectionOptions = require(configFile);
}
// normalize and return connection options
if (connectionOptions) {
return this.normalizeConnectionOptions(connectionOptions);
}
return undefined;
}
/**
* Normalize connection options.
*/
protected normalizeConnectionOptions(
connectionOptions: DataSourceOptions | DataSourceOptions[],
): DataSourceOptions[] {
if (!Array.isArray(connectionOptions)) connectionOptions = [connectionOptions];
connectionOptions.forEach((options) => {
options.baseDirectory = this.baseDirectory;
if (options.entities) {
const entities = (options.entities as any[]).map((entity) => {
if (typeof entity === 'string' && entity.substr(0, 1) !== '/')
return this.baseDirectory + '/' + entity;
return entity;
});
Object.assign(connectionOptions, { entities: entities });
}
if (options.subscribers) {
const subscribers = (options.subscribers as any[]).map((subscriber) => {
if (typeof subscriber === 'string' && subscriber.substr(0, 1) !== '/')
return this.baseDirectory + '/' + subscriber;
return subscriber;
});
Object.assign(connectionOptions, { subscribers: subscribers });
}
if (options.migrations) {
const migrations = (options.migrations as any[]).map((migration) => {
if (typeof migration === 'string' && migration.substr(0, 1) !== '/')
return this.baseDirectory + '/' + migration;
return migration;
});
Object.assign(connectionOptions, { migrations: migrations });
}
// make database path file in sqlite relative to package.json
if (options.type === 'sqlite' || options.type === 'sqlite-pooled') {
if (
typeof options.database === 'string' &&
!isAbsolute(options.database) &&
options.database.substr(0, 1) !== '/' && // unix absolute
options.database.substr(1, 2) !== ':\\' && // windows absolute
options.database !== ':memory:'
) {
Object.assign(options, {
database: this.baseDirectory + '/' + options.database,
});
}
}
});
return connectionOptions;
}
/**
* Gets directory where configuration file should be located and configuration file name.
*/
protected get baseFilePath(): string {
return path.resolve(this.baseDirectory, this.baseConfigName);
}
/**
* Gets directory where configuration file should be located.
*/
protected get baseDirectory(): string {
if (this.options && this.options.root) return this.options.root;
return appRootPath.path;
}
/**
* Gets configuration file name.
*/
protected get baseConfigName(): string {
if (this.options && this.options.configName) return this.options.configName;
return 'ormconfig';
}
}
@@ -0,0 +1,108 @@
import { DataSourceOptions } from '../../data-source/DataSourceOptions';
import { PlatformTools } from '../../platform/PlatformTools';
import { OrmUtils } from '../../util/OrmUtils';
/**
* Reads connection options from environment variables.
* Environment variables can have only a single connection.
* Its strongly required to define TYPEORM_CONNECTION env variable.
*
* @deprecated
*/
export class ConnectionOptionsEnvReader {
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Reads connection options from environment variables.
*/
async read(): Promise<DataSourceOptions[]> {
return [
{
type:
PlatformTools.getEnvVariable('TYPEORM_CONNECTION') ||
(PlatformTools.getEnvVariable('TYPEORM_URL')
? PlatformTools.getEnvVariable('TYPEORM_URL').split('://')[0]
: undefined),
url: PlatformTools.getEnvVariable('TYPEORM_URL'),
host: PlatformTools.getEnvVariable('TYPEORM_HOST'),
port: this.stringToNumber(PlatformTools.getEnvVariable('TYPEORM_PORT')),
username: PlatformTools.getEnvVariable('TYPEORM_USERNAME'),
password: PlatformTools.getEnvVariable('TYPEORM_PASSWORD'),
database: PlatformTools.getEnvVariable('TYPEORM_DATABASE'),
schema: PlatformTools.getEnvVariable('TYPEORM_SCHEMA'),
extra: PlatformTools.getEnvVariable('TYPEORM_DRIVER_EXTRA')
? JSON.parse(PlatformTools.getEnvVariable('TYPEORM_DRIVER_EXTRA'))
: undefined,
synchronize: OrmUtils.toBoolean(PlatformTools.getEnvVariable('TYPEORM_SYNCHRONIZE')),
dropSchema: OrmUtils.toBoolean(PlatformTools.getEnvVariable('TYPEORM_DROP_SCHEMA')),
migrationsRun: OrmUtils.toBoolean(PlatformTools.getEnvVariable('TYPEORM_MIGRATIONS_RUN')),
entities: this.stringToArray(PlatformTools.getEnvVariable('TYPEORM_ENTITIES')),
migrations: this.stringToArray(PlatformTools.getEnvVariable('TYPEORM_MIGRATIONS')),
migrationsTableName: PlatformTools.getEnvVariable('TYPEORM_MIGRATIONS_TABLE_NAME'),
metadataTableName: PlatformTools.getEnvVariable('TYPEORM_METADATA_TABLE_NAME'),
subscribers: this.stringToArray(PlatformTools.getEnvVariable('TYPEORM_SUBSCRIBERS')),
logging: this.transformLogging(PlatformTools.getEnvVariable('TYPEORM_LOGGING')),
logger: PlatformTools.getEnvVariable('TYPEORM_LOGGER'),
entityPrefix: PlatformTools.getEnvVariable('TYPEORM_ENTITY_PREFIX'),
maxQueryExecutionTime: PlatformTools.getEnvVariable('TYPEORM_MAX_QUERY_EXECUTION_TIME'),
cache: this.transformCaching(),
uuidExtension: PlatformTools.getEnvVariable('TYPEORM_UUID_EXTENSION'),
},
];
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Transforms logging string into real logging value connection requires.
*/
protected transformLogging(logging: string): any {
if (logging === 'true' || logging === 'TRUE' || logging === '1') return true;
if (logging === 'all') return 'all';
return this.stringToArray(logging);
}
/**
* Transforms caching option into real caching value option requires.
*/
protected transformCaching(): boolean | object | undefined {
const caching = PlatformTools.getEnvVariable('TYPEORM_CACHE');
if (caching === 'true' || caching === 'TRUE' || caching === '1') return true;
if (caching === 'false' || caching === 'FALSE' || caching === '0') return false;
if (caching === 'database')
return {
type: caching,
options: PlatformTools.getEnvVariable('TYPEORM_CACHE_OPTIONS')
? JSON.parse(PlatformTools.getEnvVariable('TYPEORM_CACHE_OPTIONS'))
: undefined,
alwaysEnabled: PlatformTools.getEnvVariable('TYPEORM_CACHE_ALWAYS_ENABLED'),
duration: parseInt(PlatformTools.getEnvVariable('TYPEORM_CACHE_DURATION')),
};
return undefined;
}
/**
* Converts a string which contains multiple elements split by comma into a string array of strings.
*/
protected stringToArray(variable?: string) {
if (!variable) return [];
return variable.split(',').map((str) => str.trim());
}
/**
* Converts a string which contains a number into a javascript number
*/
private stringToNumber(value: any): number | undefined {
if (!value) {
return undefined;
}
return parseInt(value);
}
}
+83
View File
@@ -0,0 +1,83 @@
/**
* Container options.
*
* @deprecated
*/
export interface UseContainerOptions {
/**
* If set to true, then default container will be used in the case if given container haven't returned anything.
*/
fallback?: boolean;
/**
* If set to true, then default container will be used in the case if given container thrown an exception.
*/
fallbackOnErrors?: boolean;
}
/**
* @deprecated
*/
export type ContainedType<T> = { new (...args: any[]): T } | Function;
/**
* @deprecated
*/
export interface ContainerInterface {
get<T>(someClass: ContainedType<T>): T;
}
/**
* Container to be used by this library for inversion control. If container was not implicitly set then by default
* container simply creates a new instance of the given class.
*
* @deprecated
*/
const defaultContainer: ContainerInterface = new (class implements ContainerInterface {
private instances: { type: Function; object: any }[] = [];
get<T>(someClass: ContainedType<T>): T {
let instance = this.instances.find((i) => i.type === someClass);
if (!instance) {
instance = {
type: someClass,
object: new (someClass as new () => T)(),
};
this.instances.push(instance);
}
return instance.object;
}
})();
let userContainer: ContainerInterface;
let userContainerOptions: UseContainerOptions | undefined;
/**
* Sets container to be used by this library.
*
* @deprecated
*/
export function useContainer(iocContainer: ContainerInterface, options?: UseContainerOptions) {
userContainer = iocContainer;
userContainerOptions = options;
}
/**
* Gets the IOC container used by this library.
*
* @deprecated
*/
export function getFromContainer<T>(someClass: ContainedType<T>): T {
if (userContainer) {
try {
const instance = userContainer.get(someClass);
if (instance) return instance;
if (!userContainerOptions || !userContainerOptions.fallback) return instance;
} catch (error) {
if (!userContainerOptions || !userContainerOptions.fallbackOnErrors) throw error;
}
}
return defaultContainer.get<T>(someClass);
}
@@ -0,0 +1,201 @@
import { EntitySchema } from '../entity-schema/EntitySchema';
import { LoggerOptions } from '../logger/LoggerOptions';
import { NamingStrategyInterface } from '../naming-strategy/NamingStrategyInterface';
import { DatabaseType } from '../driver/types/DatabaseType';
import { Logger } from '../logger/Logger';
import { DataSource } from '../data-source/DataSource';
import { QueryResultCache } from '../cache/QueryResultCache';
import { MixedList } from '../common/MixedList';
/**
* BaseDataSourceOptions is set of DataSourceOptions shared by all database types.
*/
export interface BaseDataSourceOptions {
/**
* Database type. This value is required.
*/
readonly type: DatabaseType;
/**
* Connection name. If connection name is not given then it will be called "default".
* Different connections must have different names.
*
* @deprecated
*/
readonly name?: string;
/**
* Entities to be loaded for this connection.
* Accepts both entity classes and directories where from entities need to be loaded.
* Directories support glob patterns.
*/
readonly entities?: MixedList<Function | string | EntitySchema>;
/**
* Subscribers to be loaded for this connection.
* Accepts both subscriber classes and directories where from subscribers need to be loaded.
* Directories support glob patterns.
*/
readonly subscribers?: MixedList<Function | string>;
/**
* Migrations to be loaded for this connection.
* Accepts both migration classes and glob patterns representing migration files.
*/
readonly migrations?: MixedList<Function | string>;
/**
* Migrations table name, in case of different name from "migrations".
* Accepts single string name.
*/
readonly migrationsTableName?: string;
/**
* Transaction mode for migrations to run in
*/
readonly migrationsTransactionMode?: 'all' | 'none' | 'each';
/**
* Typeorm metadata table name, in case of different name from "typeorm_metadata".
* Accepts single string name.
*/
readonly metadataTableName?: string;
/**
* Naming strategy to be used to name tables and columns in the database.
*/
readonly namingStrategy?: NamingStrategyInterface;
/**
* Logging options.
*/
readonly logging?: LoggerOptions;
/**
* Logger instance used to log queries and events in the ORM.
*/
readonly logger?: 'advanced-console' | 'simple-console' | 'file' | 'debug' | Logger;
/**
* Maximum number of milliseconds query should be executed before logger log a warning.
*/
readonly maxQueryExecutionTime?: number;
/**
* Maximum number of clients the pool should contain.
*/
readonly poolSize?: number;
/**
* Indicates if database schema should be auto created on every application launch.
* Be careful with this option and don't use this in production - otherwise you can lose production data.
* This option is useful during debug and development.
* Alternative to it, you can use CLI and run schema:sync command.
*
* Note that for MongoDB database it does not create schema, because MongoDB is schemaless.
* Instead, it syncs just by creating indices.
*/
readonly synchronize?: boolean;
/**
* Indicates if migrations should be auto run on every application launch.
* Alternative to it, you can use CLI and run migrations:run command.
*/
readonly migrationsRun?: boolean;
/**
* Drops the schema each time connection is being established.
* Be careful with this option and don't use this in production - otherwise you'll lose all production data.
* This option is useful during debug and development.
*/
readonly dropSchema?: boolean;
/**
* Prefix to use on all tables (collections) of this connection in the database.
*/
readonly entityPrefix?: string;
/**
* When creating new Entity instances, skip all constructors when true.
*/
readonly entitySkipConstructor?: boolean;
/**
* Extra connection options to be passed to the underlying driver.
*
* todo: deprecate this and move all database-specific types into hts own connection options object.
*/
readonly extra?: any;
/**
* Specifies how relations must be loaded - using "joins" or separate queries.
* If you are loading too much data with nested joins it's better to load relations
* using separate queries.
*
* Default strategy is "join", but this default can be changed here.
* Also, strategy can be set per-query in FindOptions and QueryBuilder.
*/
readonly relationLoadStrategy?: 'join' | 'query';
/**
* Optionally applied "typename" to the model.
* If set, then each hydrated model will have this property with the target model / entity name inside.
*
* (works like a discriminator property).
*/
readonly typename?: string;
/**
* Holds reference to the baseDirectory where configuration file are expected.
*
* @internal
*/
baseDirectory?: string;
/**
* Allows to setup cache options.
*/
readonly cache?:
| boolean
| {
/**
* Type of caching.
*
* - "database" means cached values will be stored in the separate table in database. This is default value.
*/
readonly type?: 'database';
/**
* Factory function for custom cache providers that implement QueryResultCache.
*/
readonly provider?: (connection: DataSource) => QueryResultCache;
/**
* Configurable table name for "database" type cache.
* Default value is "query-result-cache"
*/
readonly tableName?: string;
/**
* If set to true then queries (using find methods and QueryBuilder's methods) will always be cached.
*/
readonly alwaysEnabled?: boolean;
/**
* Time in milliseconds in which cache will expire.
* This can be setup per-query.
* Default value is 1000 which is equivalent to 1 second.
*/
readonly duration?: number;
/**
* Used to specify if cache errors should be ignored, and pass through the call to the Database.
*/
readonly ignoreErrors?: boolean;
};
/**
* Allows automatic isolation of where clauses
*/
readonly isolateWhereStatements?: boolean;
}
@@ -0,0 +1,632 @@
import { Driver } from '../driver/Driver';
import { registerQueryBuilders } from '../query-builder';
import { Repository } from '../repository/Repository';
import { EntitySubscriberInterface } from '../subscriber/EntitySubscriberInterface';
import { EntityTarget } from '../common/EntityTarget';
import { ObjectType } from '../common/ObjectType';
import { EntityManager } from '../entity-manager/EntityManager';
import { DefaultNamingStrategy } from '../naming-strategy/DefaultNamingStrategy';
import {
CannotConnectAlreadyConnectedError,
CannotExecuteNotConnectedError,
EntityMetadataNotFoundError,
QueryRunnerProviderAlreadyReleasedError,
TypeORMError,
} from '../error';
import { TreeRepository } from '../repository/TreeRepository';
import { NamingStrategyInterface } from '../naming-strategy/NamingStrategyInterface';
import { EntityMetadata } from '../metadata/EntityMetadata';
import { Logger } from '../logger/Logger';
import { MigrationInterface } from '../migration/MigrationInterface';
import { MigrationExecutor } from '../migration/MigrationExecutor';
import { Migration } from '../migration/Migration';
import { EntityMetadataValidator } from '../metadata-builder/EntityMetadataValidator';
import { DataSourceOptions } from './DataSourceOptions';
import { EntityManagerFactory } from '../entity-manager/EntityManagerFactory';
import { DriverFactory } from '../driver/DriverFactory';
import { ConnectionMetadataBuilder } from '../connection/ConnectionMetadataBuilder';
import { QueryRunner } from '../query-runner/QueryRunner';
import { SelectQueryBuilder } from '../query-builder/SelectQueryBuilder';
import { LoggerFactory } from '../logger/LoggerFactory';
import { QueryResultCacheFactory } from '../cache/QueryResultCacheFactory';
import { QueryResultCache } from '../cache/QueryResultCache';
import { RelationLoader } from '../query-builder/RelationLoader';
import { ObjectUtils } from '../util/ObjectUtils';
import { IsolationLevel } from '../driver/types/IsolationLevel';
import { ReplicationMode } from '../driver/types/ReplicationMode';
import { RelationIdLoader } from '../query-builder/RelationIdLoader';
import { DriverUtils } from '../driver/DriverUtils';
import { InstanceChecker } from '../util/InstanceChecker';
import { ObjectLiteral } from '../common/ObjectLiteral';
registerQueryBuilders();
/**
* DataSource is a pre-defined connection configuration to a specific database.
* You can have multiple data sources connected (with multiple connections in it),
* connected to multiple databases in your application.
*
* Before, it was called `Connection`, but now `Connection` is deprecated
* because `Connection` isn't the best name for what it's actually is.
*/
export class DataSource {
readonly '@instanceof' = Symbol.for('DataSource');
// -------------------------------------------------------------------------
// Public Readonly Properties
// -------------------------------------------------------------------------
/**
* Connection name.
*
* @deprecated we don't need names anymore since we are going to drop all related methods relying on this property.
*/
readonly name: string;
/**
* Connection options.
*/
readonly options: DataSourceOptions;
/**
* Indicates if DataSource is initialized or not.
*/
readonly isInitialized: boolean;
/**
* Database driver used by this connection.
*/
driver: Driver;
/**
* EntityManager of this connection.
*/
readonly manager: EntityManager;
/**
* Naming strategy used in the connection.
*/
namingStrategy: NamingStrategyInterface;
/**
* Name for the metadata table
*/
readonly metadataTableName: string;
/**
* Logger used to log orm events.
*/
logger: Logger;
/**
* Migration instances that are registered for this connection.
*/
readonly migrations: MigrationInterface[] = [];
/**
* Entity subscriber instances that are registered for this connection.
*/
readonly subscribers: EntitySubscriberInterface<any>[] = [];
/**
* All entity metadatas that are registered for this connection.
*/
readonly entityMetadatas: EntityMetadata[] = [];
/**
* All entity metadatas that are registered for this connection.
* This is a copy of #.entityMetadatas property -> used for more performant searches.
*/
readonly entityMetadatasMap = new Map<EntityTarget<any>, EntityMetadata>();
/**
* Used to work with query result cache.
*/
queryResultCache?: QueryResultCache;
/**
* Used to load relations and work with lazy relations.
*/
readonly relationLoader: RelationLoader;
readonly relationIdLoader: RelationIdLoader;
// -------------------------------------------------------------------------
// Constructor
// -------------------------------------------------------------------------
constructor(options: DataSourceOptions) {
registerQueryBuilders();
this.name = options.name || 'default';
this.options = options;
this.logger = new LoggerFactory().create(this.options.logger, this.options.logging);
this.manager = this.createEntityManager();
this.namingStrategy = options.namingStrategy || new DefaultNamingStrategy();
this.metadataTableName = options.metadataTableName || 'typeorm_metadata';
this.relationLoader = new RelationLoader(this);
this.relationIdLoader = new RelationIdLoader(this);
this.isInitialized = false;
}
// -------------------------------------------------------------------------
// Public Accessors
// -------------------------------------------------------------------------
/**
Indicates if DataSource is initialized or not.
*
* @deprecated use .isInitialized instead
*/
get isConnected() {
return this.isInitialized;
}
// -------------------------------------------------------------------------
// Public Methods
// -------------------------------------------------------------------------
/**
* Updates current connection options with provided options.
*/
setOptions(options: Partial<DataSourceOptions>): this {
Object.assign(this.options, options);
if (options.logger || options.logging) {
this.logger = new LoggerFactory().create(
options.logger || this.options.logger,
options.logging || this.options.logging,
);
}
if (options.namingStrategy) {
this.namingStrategy = options.namingStrategy;
}
if (options.cache) {
this.queryResultCache = new QueryResultCacheFactory(this).create();
}
// todo: we must update the database in the driver as well, if it was set by setOptions method
// in the future we need to refactor the code and remove "database" from the driver, and instead
// use database (and options) from a single place - data source.
if (options.database) {
this.driver.database = DriverUtils.buildDriverOptions(this.options).database;
}
// todo: need to take a look if we need to update schema and other "poor" properties
return this;
}
/**
* Performs connection to the database.
* This method should be called once on application bootstrap.
* This method not necessarily creates database connection (depend on database type),
* but it also can setup a connection pool with database to use.
*/
async initialize(): Promise<this> {
if (this.isInitialized) throw new CannotConnectAlreadyConnectedError(this.name);
this.driver = await DriverFactory.create(this);
this.queryResultCache = this.options.cache
? new QueryResultCacheFactory(this).create()
: undefined;
// connect to the database via its driver
await this.driver.connect();
// connect to the cache-specific database if cache is enabled
if (this.queryResultCache) await this.queryResultCache.connect();
// set connected status for the current connection
ObjectUtils.assign(this, { isInitialized: true });
try {
// build all metadatas registered in the current connection
await this.buildMetadatas();
await this.driver.afterConnect();
// if option is set - drop schema once connection is done
if (this.options.dropSchema) await this.dropDatabase();
// if option is set - automatically synchronize a schema
if (this.options.migrationsRun)
await this.runMigrations({
transaction: this.options.migrationsTransactionMode,
});
// if option is set - automatically synchronize a schema
if (this.options.synchronize) await this.synchronize();
} catch (error) {
// if for some reason build metadata fail (for example validation error during entity metadata check)
// connection needs to be closed
await this.destroy();
throw error;
}
return this;
}
/**
* Performs connection to the database.
* This method should be called once on application bootstrap.
* This method not necessarily creates database connection (depend on database type),
* but it also can setup a connection pool with database to use.
*
* @deprecated use .initialize method instead
*/
async connect(): Promise<this> {
return this.initialize();
}
/**
* Closes connection with the database.
* Once connection is closed, you cannot use repositories or perform any operations except opening connection again.
*/
async destroy(): Promise<void> {
if (!this.isInitialized) throw new CannotExecuteNotConnectedError(this.name);
await this.driver.disconnect();
// disconnect from the cache-specific database if cache was enabled
if (this.queryResultCache) await this.queryResultCache.disconnect();
ObjectUtils.assign(this, { isInitialized: false });
}
/**
* Closes connection with the database.
* Once connection is closed, you cannot use repositories or perform any operations except opening connection again.
*
* @deprecated use .destroy method instead
*/
async close(): Promise<void> {
return this.destroy();
}
/**
* Creates database schema for all entities registered in this connection.
* Can be used only after connection to the database is established.
*
* @param dropBeforeSync If set to true then it drops the database with all its tables and data
*/
async synchronize(dropBeforeSync: boolean = false): Promise<void> {
if (!this.isInitialized) throw new CannotExecuteNotConnectedError(this.name);
if (dropBeforeSync) await this.dropDatabase();
const schemaBuilder = this.driver.createSchemaBuilder();
await schemaBuilder.build();
}
/**
* Drops the database and all its data.
* Be careful with this method on production since this method will erase all your database tables and their data.
* Can be used only after connection to the database is established.
*/
// TODO rename
async dropDatabase(): Promise<void> {
const queryRunner = this.createQueryRunner();
try {
if (DriverUtils.isSQLiteFamily(this.driver)) {
const databases: string[] = [];
this.entityMetadatas.forEach((metadata) => {
if (metadata.database && databases.indexOf(metadata.database) === -1)
databases.push(metadata.database);
});
if (databases.length === 0 && this.driver.database) {
databases.push(this.driver.database);
}
if (databases.length === 0) {
await queryRunner.clearDatabase();
} else {
for (const database of databases) {
await queryRunner.clearDatabase(database);
}
}
} else {
await queryRunner.clearDatabase();
}
} finally {
await queryRunner.release();
}
}
/**
* Runs all pending migrations.
* Can be used only after connection to the database is established.
*/
async runMigrations(options?: {
transaction?: 'all' | 'none' | 'each';
fake?: boolean;
}): Promise<Migration[]> {
if (!this.isInitialized) throw new CannotExecuteNotConnectedError(this.name);
const migrationExecutor = new MigrationExecutor(this);
migrationExecutor.transaction =
options?.transaction || this.options?.migrationsTransactionMode || 'all';
migrationExecutor.fake = (options && options.fake) || false;
const successMigrations = await migrationExecutor.executePendingMigrations();
return successMigrations;
}
/**
* Reverts last executed migration.
* Can be used only after connection to the database is established.
*/
async undoLastMigration(options?: {
transaction?: 'all' | 'none' | 'each';
fake?: boolean;
}): Promise<void> {
if (!this.isInitialized) throw new CannotExecuteNotConnectedError(this.name);
const migrationExecutor = new MigrationExecutor(this);
migrationExecutor.transaction = (options && options.transaction) || 'all';
migrationExecutor.fake = (options && options.fake) || false;
await migrationExecutor.undoLastMigration();
}
/**
* Lists all migrations and whether they have been run.
* Returns true if there are pending migrations
*/
async showMigrations(): Promise<boolean> {
if (!this.isInitialized) {
throw new CannotExecuteNotConnectedError(this.name);
}
const migrationExecutor = new MigrationExecutor(this);
return await migrationExecutor.showMigrations();
}
/**
* Checks if entity metadata exist for the given entity class, target name or table name.
*/
hasMetadata(target: EntityTarget<any>): boolean {
return !!this.findMetadata(target);
}
/**
* Gets entity metadata for the given entity class or schema name.
*/
getMetadata(target: EntityTarget<any>): EntityMetadata {
const metadata = this.findMetadata(target);
if (!metadata) throw new EntityMetadataNotFoundError(target);
return metadata;
}
/**
* Gets repository for the given entity.
*/
getRepository<Entity extends ObjectLiteral>(target: EntityTarget<Entity>): Repository<Entity> {
return this.manager.getRepository(target);
}
/**
* Gets tree repository for the given entity class or name.
* Only tree-type entities can have a TreeRepository, like ones decorated with @Tree decorator.
*/
getTreeRepository<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>,
): TreeRepository<Entity> {
return this.manager.getTreeRepository(target);
}
/**
* Gets custom entity repository marked with @EntityRepository decorator.
*
* @deprecated use Repository.extend function to create a custom repository
*/
getCustomRepository<T>(customRepository: ObjectType<T>): T {
return this.manager.getCustomRepository(customRepository);
}
/**
* Wraps given function execution (and all operations made there) into a transaction.
* All database operations must be executed using provided entity manager.
*/
async transaction<T>(runInTransaction: (entityManager: EntityManager) => Promise<T>): Promise<T>;
async transaction<T>(
isolationLevel: IsolationLevel,
runInTransaction: (entityManager: EntityManager) => Promise<T>,
): Promise<T>;
async transaction<T>(
isolationOrRunInTransaction: IsolationLevel | ((entityManager: EntityManager) => Promise<T>),
runInTransactionParam?: (entityManager: EntityManager) => Promise<T>,
): Promise<any> {
return this.manager.transaction(
isolationOrRunInTransaction as any,
runInTransactionParam as any,
);
}
/**
* Executes raw SQL query and returns raw database results.
*/
async query<T = any>(query: string, parameters?: any[], queryRunner?: QueryRunner): Promise<T> {
if (queryRunner && queryRunner.isReleased) throw new QueryRunnerProviderAlreadyReleasedError();
const usedQueryRunner = queryRunner || this.createQueryRunner();
try {
return await usedQueryRunner.query(query, parameters); // await is needed here because we are using finally
} finally {
if (!queryRunner) await usedQueryRunner.release();
}
}
/**
* Creates a new query builder that can be used to build a SQL query.
*/
createQueryBuilder<Entity extends ObjectLiteral>(
entityClass: EntityTarget<Entity>,
alias: string,
queryRunner?: QueryRunner,
): SelectQueryBuilder<Entity>;
/**
* Creates a new query builder that can be used to build a SQL query.
*/
createQueryBuilder(queryRunner?: QueryRunner): SelectQueryBuilder<any>;
/**
* Creates a new query builder that can be used to build a SQL query.
*/
createQueryBuilder<Entity extends ObjectLiteral>(
entityOrRunner?: EntityTarget<Entity> | QueryRunner,
alias?: string,
queryRunner?: QueryRunner,
): SelectQueryBuilder<Entity> {
if (alias) {
alias = DriverUtils.buildAlias(this.driver, undefined, alias);
const metadata = this.getMetadata(entityOrRunner as EntityTarget<Entity>);
return new SelectQueryBuilder(this, queryRunner).select(alias).from(metadata.target, alias);
} else {
return new SelectQueryBuilder(this, entityOrRunner as QueryRunner | undefined);
}
}
/**
* Creates a query runner used for perform queries on a single database connection.
* Using query runners you can control your queries to execute using single database connection and
* manually control your database transaction.
*
* Mode is used in replication mode and indicates whatever you want to connect
* to master database or any of slave databases.
* If you perform writes you must use master database,
* if you perform reads you can use slave databases.
*/
createQueryRunner(mode: ReplicationMode = 'master'): QueryRunner {
const queryRunner = this.driver.createQueryRunner(mode);
const manager = this.createEntityManager(queryRunner);
Object.assign(queryRunner, { manager: manager });
return queryRunner;
}
/**
* Gets entity metadata of the junction table (many-to-many table).
*/
getManyToManyMetadata(entityTarget: EntityTarget<any>, relationPropertyPath: string) {
const relationMetadata =
this.getMetadata(entityTarget).findRelationWithPropertyPath(relationPropertyPath);
if (!relationMetadata)
throw new TypeORMError(
`Relation "${relationPropertyPath}" was not found in ${entityTarget} entity.`,
);
if (!relationMetadata.isManyToMany)
throw new TypeORMError(
`Relation "${entityTarget}#${relationPropertyPath}" does not have a many-to-many relationship.` +
`You can use this method only on many-to-many relations.`,
);
return relationMetadata.junctionEntityMetadata;
}
/**
* Creates an Entity Manager for the current connection with the help of the EntityManagerFactory.
*/
createEntityManager(queryRunner?: QueryRunner): EntityManager {
return new EntityManagerFactory().create(this, queryRunner);
}
// -------------------------------------------------------------------------
// Protected Methods
// -------------------------------------------------------------------------
/**
* Finds exist entity metadata by the given entity class, target name or table name.
*/
protected findMetadata(target: EntityTarget<any>): EntityMetadata | undefined {
const metadataFromMap = this.entityMetadatasMap.get(target);
if (metadataFromMap) return metadataFromMap;
for (let [_, metadata] of this.entityMetadatasMap) {
if (InstanceChecker.isEntitySchema(target) && metadata.name === target.options.name) {
return metadata;
}
if (typeof target === 'string') {
if (target.indexOf('.') !== -1) {
if (metadata.tablePath === target) {
return metadata;
}
} else {
if (metadata.name === target || metadata.tableName === target) {
return metadata;
}
}
}
if (ObjectUtils.isObjectWithName(target) && typeof target.name === 'string') {
if (target.name.indexOf('.') !== -1) {
if (metadata.tablePath === target.name) {
return metadata;
}
} else {
if (metadata.name === target.name || metadata.tableName === target.name) {
return metadata;
}
}
}
}
return undefined;
}
/**
* Builds metadatas for all registered classes inside this connection.
*/
protected async buildMetadatas(): Promise<void> {
const connectionMetadataBuilder = new ConnectionMetadataBuilder(this);
const entityMetadataValidator = new EntityMetadataValidator();
// create subscribers instances if they are not disallowed from high-level (for example they can disallowed from migrations run process)
const flattenedSubscribers = ObjectUtils.mixedListToArray(this.options.subscribers || []);
const subscribers = await connectionMetadataBuilder.buildSubscribers(flattenedSubscribers);
ObjectUtils.assign(this, { subscribers: subscribers });
// build entity metadatas
const flattenedEntities = ObjectUtils.mixedListToArray(this.options.entities || []);
const entityMetadatas = await connectionMetadataBuilder.buildEntityMetadatas(flattenedEntities);
ObjectUtils.assign(this, {
entityMetadatas: entityMetadatas,
entityMetadatasMap: new Map(entityMetadatas.map((metadata) => [metadata.target, metadata])),
});
// create migration instances
const flattenedMigrations = ObjectUtils.mixedListToArray(this.options.migrations || []);
const migrations = await connectionMetadataBuilder.buildMigrations(flattenedMigrations);
ObjectUtils.assign(this, { migrations: migrations });
// validate all created entity metadatas to make sure user created entities are valid and correct
entityMetadataValidator.validateMany(
this.entityMetadatas.filter((metadata) => metadata.tableType !== 'view'),
this.driver,
);
// set current data source to the entities
for (let entityMetadata of entityMetadatas) {
if (InstanceChecker.isBaseEntityConstructor(entityMetadata.target)) {
entityMetadata.target.useDataSource(this);
}
}
}
/**
* Get the replication mode SELECT queries should use for this datasource by default
*/
defaultReplicationModeForReads(): ReplicationMode {
if ('replication' in this.driver.options) {
const value = (
this.driver.options.replication as {
defaultMode?: ReplicationMode;
}
).defaultMode;
if (value) {
return value;
}
}
return 'slave';
}
}
@@ -0,0 +1,11 @@
import { PostgresConnectionOptions } from '../driver/postgres/PostgresConnectionOptions';
import { SqliteConnectionOptions } from '../driver/sqlite/SqliteConnectionOptions';
import { SqlitePooledConnectionOptions } from '../driver/sqlite-pooled/SqlitePooledConnectionOptions';
/**
* DataSourceOptions is an interface with settings and options for specific DataSource.
*/
export type DataSourceOptions =
| PostgresConnectionOptions
| SqliteConnectionOptions
| SqlitePooledConnectionOptions;
@@ -0,0 +1,2 @@
export * from './DataSource';
export * from './DataSourceOptions';
@@ -0,0 +1,40 @@
import { getMetadataArgsStorage } from '../globals';
import { CheckMetadataArgs } from '../metadata-args/CheckMetadataArgs';
import { TypeORMError } from '../error';
/**
* Creates a database check.
* Can be used on entity property or on entity.
* Can create checks with composite columns when used on entity.
*/
export function Check(expression: string): ClassDecorator & PropertyDecorator;
/**
* Creates a database check.
* Can be used on entity property or on entity.
* Can create checks with composite columns when used on entity.
*/
export function Check(name: string, expression: string): ClassDecorator & PropertyDecorator;
/**
* Creates a database check.
* Can be used on entity property or on entity.
* Can create checks with composite columns when used on entity.
*/
export function Check(
nameOrExpression: string,
maybeExpression?: string,
): ClassDecorator & PropertyDecorator {
const name = maybeExpression ? nameOrExpression : undefined;
const expression = maybeExpression ? maybeExpression : nameOrExpression;
if (!expression) throw new TypeORMError(`Check expression is required`);
return function (clsOrObject: Function | Object, propertyName?: string | symbol) {
getMetadataArgsStorage().checks.push({
target: propertyName ? clsOrObject.constructor : (clsOrObject as Function),
name: name,
expression: expression,
} as CheckMetadataArgs);
};
}
@@ -0,0 +1,19 @@
import { getMetadataArgsStorage } from '../globals';
import { EntityRepositoryMetadataArgs } from '../metadata-args/EntityRepositoryMetadataArgs';
import { EntitySchema } from '../entity-schema/EntitySchema';
/**
* Used to declare a class as a custom repository.
* Custom repository can manage some specific entity or just be generic.
* Custom repository optionally can extend AbstractRepository, Repository or TreeRepository.
*
* @deprecated use Repository.extend function to create a custom repository
*/
export function EntityRepository(entity?: Function | EntitySchema<any>): ClassDecorator {
return function (target: Function) {
getMetadataArgsStorage().entityRepositories.push({
target: target,
entity: entity,
} as EntityRepositoryMetadataArgs);
};
}
@@ -0,0 +1,40 @@
import { getMetadataArgsStorage } from '../globals';
import { ExclusionMetadataArgs } from '../metadata-args/ExclusionMetadataArgs';
import { TypeORMError } from '../error';
/**
* Creates a database exclusion.
* Can be used on entity.
* Can create exclusions with composite columns when used on entity.
*/
export function Exclusion(expression: string): ClassDecorator & PropertyDecorator;
/**
* Creates a database exclusion.
* Can be used on entity.
* Can create exclusions with composite columns when used on entity.
*/
export function Exclusion(name: string, expression: string): ClassDecorator & PropertyDecorator;
/**
* Creates a database exclusion.
* Can be used on entity.
* Can create exclusions with composite columns when used on entity.
*/
export function Exclusion(
nameOrExpression: string,
maybeExpression?: string,
): ClassDecorator & PropertyDecorator {
const name = maybeExpression ? nameOrExpression : undefined;
const expression = maybeExpression ? maybeExpression : nameOrExpression;
if (!expression) throw new TypeORMError(`Exclusion expression is required`);
return function (clsOrObject: Function | Object, propertyName?: string | symbol) {
getMetadataArgsStorage().exclusions.push({
target: propertyName ? clsOrObject.constructor : (clsOrObject as Function),
name: name,
expression: expression,
} as ExclusionMetadataArgs);
};
}
@@ -0,0 +1,23 @@
import { getMetadataArgsStorage } from '../globals';
import { GeneratedMetadataArgs } from '../metadata-args/GeneratedMetadataArgs';
/**
* Marks a column to generate a value on entity insertion.
* There are three types of generation strategy - increment, uuid and rowid (cockroachdb only).
* Increment uses a number which increases by one on each insertion.
* Uuid generates a special UUID token.
* Rowid supports only in CockroachDB and uses `unique_rowid()` function
*
* Note, some databases do not support non-primary generation columns.
*/
export function Generated(
strategy: 'increment' | 'uuid' | 'rowid' = 'increment',
): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().generations.push({
target: object.constructor,
propertyName: propertyName,
strategy: strategy,
} as GeneratedMetadataArgs);
};
}
@@ -0,0 +1,122 @@
import { getMetadataArgsStorage } from '../globals';
import { IndexMetadataArgs } from '../metadata-args/IndexMetadataArgs';
import { IndexOptions } from './options/IndexOptions';
import { ObjectUtils } from '../util/ObjectUtils';
/**
* Creates a database index.
* Can be used on entity property or on entity.
* Can create indices with composite columns when used on entity.
*/
export function Index(options?: IndexOptions): ClassDecorator & PropertyDecorator;
/**
* Creates a database index.
* Can be used on entity property or on entity.
* Can create indices with composite columns when used on entity.
*/
export function Index(name: string, options?: IndexOptions): ClassDecorator & PropertyDecorator;
/**
* Creates a database index.
* Can be used on entity property or on entity.
* Can create indices with composite columns when used on entity.
*/
export function Index(
name: string,
options: { synchronize: false },
): ClassDecorator & PropertyDecorator;
/**
* Creates a database index.
* Can be used on entity property or on entity.
* Can create indices with composite columns when used on entity.
*/
export function Index(
name: string,
fields: string[],
options?: IndexOptions,
): ClassDecorator & PropertyDecorator;
/**
* Creates a database index.
* Can be used on entity property or on entity.
* Can create indices with composite columns when used on entity.
*/
export function Index(fields: string[], options?: IndexOptions): ClassDecorator & PropertyDecorator;
/**
* Creates a database index.
* Can be used on entity property or on entity.
* Can create indices with composite columns when used on entity.
*/
export function Index(
fields: (object?: any) => any[] | { [key: string]: number },
options?: IndexOptions,
): ClassDecorator & PropertyDecorator;
/**
* Creates a database index.
* Can be used on entity property or on entity.
* Can create indices with composite columns when used on entity.
*/
export function Index(
name: string,
fields: (object?: any) => any[] | { [key: string]: number },
options?: IndexOptions,
): ClassDecorator & PropertyDecorator;
/**
* Creates a database index.
* Can be used on entity property or on entity.
* Can create indices with composite columns when used on entity.
*/
export function Index(
nameOrFieldsOrOptions?:
| string
| string[]
| ((object: any) => any[] | { [key: string]: number })
| IndexOptions,
maybeFieldsOrOptions?:
| ((object?: any) => any[] | { [key: string]: number })
| IndexOptions
| string[]
| { synchronize: false },
maybeOptions?: IndexOptions,
): ClassDecorator & PropertyDecorator {
// normalize parameters
const name = typeof nameOrFieldsOrOptions === 'string' ? nameOrFieldsOrOptions : undefined;
const fields =
typeof nameOrFieldsOrOptions === 'string'
? <((object?: any) => any[] | { [key: string]: number }) | string[]>maybeFieldsOrOptions
: (nameOrFieldsOrOptions as string[]);
let options =
ObjectUtils.isObject(nameOrFieldsOrOptions) && !Array.isArray(nameOrFieldsOrOptions)
? (nameOrFieldsOrOptions as IndexOptions)
: maybeOptions;
if (!options)
options =
ObjectUtils.isObject(maybeFieldsOrOptions) && !Array.isArray(maybeFieldsOrOptions)
? (maybeFieldsOrOptions as IndexOptions)
: maybeOptions;
return function (clsOrObject: Function | Object, propertyName?: string | symbol) {
getMetadataArgsStorage().indices.push({
target: propertyName ? clsOrObject.constructor : (clsOrObject as Function),
name: name,
columns: propertyName ? [propertyName] : fields,
synchronize:
options && (options as { synchronize: false }).synchronize === false ? false : true,
where: options ? options.where : undefined,
unique: options && options.unique ? true : false,
spatial: options && options.spatial ? true : false,
fulltext: options && options.fulltext ? true : false,
nullFiltered: options && options.nullFiltered ? true : false,
parser: options ? options.parser : undefined,
sparse: options && options.sparse ? true : false,
background: options && options.background ? true : false,
concurrent: options && options.concurrent ? true : false,
expireAfterSeconds: options ? options.expireAfterSeconds : undefined,
} as IndexMetadataArgs);
};
}
@@ -0,0 +1,93 @@
import { getMetadataArgsStorage } from '../globals';
import { UniqueMetadataArgs } from '../metadata-args/UniqueMetadataArgs';
import { UniqueOptions } from './options/UniqueOptions';
import { ObjectUtils } from '../util/ObjectUtils';
/**
* Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
*/
export function Unique(
name: string,
fields: string[],
options?: UniqueOptions,
): ClassDecorator & PropertyDecorator;
/**
* Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
*/
export function Unique(
fields: string[],
options?: UniqueOptions,
): ClassDecorator & PropertyDecorator;
/**
* Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
*/
export function Unique(
fields: (object?: any) => any[] | { [key: string]: number },
options?: UniqueOptions,
): ClassDecorator & PropertyDecorator;
/**
* Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
*/
export function Unique(
name: string,
fields: (object?: any) => any[] | { [key: string]: number },
options?: UniqueOptions,
): ClassDecorator & PropertyDecorator;
/**
* Composite unique constraint must be set on entity classes and must specify entity's fields to be unique.
*/
export function Unique(
nameOrFieldsOrOptions?:
| string
| string[]
| ((object: any) => any[] | { [key: string]: number })
| UniqueOptions,
maybeFieldsOrOptions?:
| ((object?: any) => any[] | { [key: string]: number })
| string[]
| UniqueOptions,
maybeOptions?: UniqueOptions,
): ClassDecorator & PropertyDecorator {
const name = typeof nameOrFieldsOrOptions === 'string' ? nameOrFieldsOrOptions : undefined;
const fields =
typeof nameOrFieldsOrOptions === 'string'
? <((object?: any) => any[] | { [key: string]: number }) | string[]>maybeFieldsOrOptions
: (nameOrFieldsOrOptions as string[]);
let options =
ObjectUtils.isObject(nameOrFieldsOrOptions) && !Array.isArray(nameOrFieldsOrOptions)
? (nameOrFieldsOrOptions as UniqueOptions)
: maybeOptions;
if (!options)
options =
ObjectUtils.isObject(nameOrFieldsOrOptions) && !Array.isArray(maybeFieldsOrOptions)
? (maybeFieldsOrOptions as UniqueOptions)
: maybeOptions;
return function (clsOrObject: Function | Object, propertyName?: string | symbol) {
let columns = fields;
if (propertyName !== undefined) {
switch (typeof propertyName) {
case 'string':
columns = [propertyName];
break;
case 'symbol':
columns = [propertyName.toString()];
break;
}
}
const args: UniqueMetadataArgs = {
target: propertyName ? clsOrObject.constructor : (clsOrObject as Function),
name: name,
columns,
deferrable: options ? options.deferrable : undefined,
};
getMetadataArgsStorage().uniques.push(args);
};
}
@@ -0,0 +1,200 @@
import { getMetadataArgsStorage } from '../../globals';
import {
ColumnType,
SimpleColumnType,
SpatialColumnType,
WithLengthColumnType,
WithPrecisionColumnType,
WithWidthColumnType,
} from '../../driver/types/ColumnTypes';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { ColumnCommonOptions } from '../options/ColumnCommonOptions';
import { SpatialColumnOptions } from '../options/SpatialColumnOptions';
import { ColumnWithLengthOptions } from '../options/ColumnWithLengthOptions';
import { ColumnNumericOptions } from '../options/ColumnNumericOptions';
import { ColumnEnumOptions } from '../options/ColumnEnumOptions';
import { ColumnEmbeddedOptions } from '../options/ColumnEmbeddedOptions';
import { EmbeddedMetadataArgs } from '../../metadata-args/EmbeddedMetadataArgs';
import { ColumnTypeUndefinedError } from '../../error/ColumnTypeUndefinedError';
import { ColumnHstoreOptions } from '../options/ColumnHstoreOptions';
import { ColumnWithWidthOptions } from '../options/ColumnWithWidthOptions';
import { GeneratedMetadataArgs } from '../../metadata-args/GeneratedMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
/**
* Column decorator is used to mark a specific class property as a table column. Only properties decorated with this
* decorator will be persisted to the database when entity be saved.
*/
export function Column(): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(options: ColumnOptions): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(type: SimpleColumnType, options?: ColumnCommonOptions): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
type: SpatialColumnType,
options?: ColumnCommonOptions & SpatialColumnOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
type: WithLengthColumnType,
options?: ColumnCommonOptions & ColumnWithLengthOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
type: WithWidthColumnType,
options?: ColumnCommonOptions & ColumnWithWidthOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
type: WithPrecisionColumnType,
options?: ColumnCommonOptions & ColumnNumericOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
type: 'enum',
options?: ColumnCommonOptions & ColumnEnumOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
type: 'simple-enum',
options?: ColumnCommonOptions & ColumnEnumOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
type: 'set',
options?: ColumnCommonOptions & ColumnEnumOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
type: 'hstore',
options?: ColumnCommonOptions & ColumnHstoreOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*
* Property in entity can be marked as Embedded, and on persist all columns from the embedded are mapped to the
* single table of the entity where Embedded is used. And on hydration all columns which supposed to be in the
* embedded will be mapped to it from the single table.
*/
export function Column(
type: (type?: any) => Function,
options?: ColumnEmbeddedOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
*/
export function Column(
typeOrOptions?: ((type?: any) => Function) | ColumnType | (ColumnOptions & ColumnEmbeddedOptions),
options?: ColumnOptions & ColumnEmbeddedOptions,
): PropertyDecorator {
return function (object: Object, propertyName: string) {
// normalize parameters
let type: ColumnType | undefined;
if (typeof typeOrOptions === 'string' || typeof typeOrOptions === 'function') {
type = <ColumnType>typeOrOptions;
} else if (typeOrOptions) {
options = <ColumnOptions>typeOrOptions;
type = typeOrOptions.type;
}
if (!options) options = {} as ColumnOptions;
// if type is not given explicitly then try to guess it
const reflectMetadataType =
Reflect && (Reflect as any).getMetadata
? (Reflect as any).getMetadata('design:type', object, propertyName)
: undefined;
if (!type && reflectMetadataType)
// if type is not given explicitly then try to guess it
type = reflectMetadataType;
// check if there is no type in column options then set type from first function argument, or guessed one
if (!options.type && type) options.type = type;
// specify HSTORE type if column is HSTORE
if (options.type === 'hstore' && !options.hstoreType)
options.hstoreType = reflectMetadataType === Object ? 'object' : 'string';
if (typeof typeOrOptions === 'function') {
// register an embedded
getMetadataArgsStorage().embeddeds.push({
target: object.constructor,
propertyName: propertyName,
isArray: reflectMetadataType === Array || options.array === true,
prefix: options.prefix !== undefined ? options.prefix : undefined,
type: typeOrOptions as (type?: any) => Function,
} as EmbeddedMetadataArgs);
} else {
// register a regular column
// if we still don't have a type then we need to give error to user that type is required
if (!options.type) throw new ColumnTypeUndefinedError(object, propertyName);
// create unique
if (options.unique === true)
getMetadataArgsStorage().uniques.push({
target: object.constructor,
columns: [propertyName],
});
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'regular',
options: options,
} as ColumnMetadataArgs);
if (options.generated) {
getMetadataArgsStorage().generations.push({
target: object.constructor,
propertyName: propertyName,
strategy: typeof options.generated === 'string' ? options.generated : 'increment',
} as GeneratedMetadataArgs);
}
}
};
}
@@ -0,0 +1,19 @@
import { getMetadataArgsStorage } from '../../globals';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
/**
* This column will store a creation date of the inserted object.
* Creation date is generated and inserted only once,
* at the first time when you create an object, the value is inserted into the table, and is never touched again.
*/
export function CreateDateColumn(options?: ColumnOptions): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'createDate',
options: options || {},
} as ColumnMetadataArgs);
};
}
@@ -0,0 +1,18 @@
import { getMetadataArgsStorage } from '../../globals';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
/**
* This column will store a delete date of the soft-deleted object.
* This date is being updated each time you soft-delete the object.
*/
export function DeleteDateColumn(options?: ColumnOptions): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'deleteDate',
options: options || {},
} as ColumnMetadataArgs);
};
}
@@ -0,0 +1,24 @@
import { getMetadataArgsStorage } from '../../globals';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
/**
* Special type of column that is available only for MongoDB database.
* Marks your entity's column to be an object id.
*/
export function ObjectIdColumn(options?: ColumnOptions): PropertyDecorator {
return function (object: Object, propertyName: string) {
// if column options are not given then create a new empty options
if (!options) options = {} as ColumnOptions;
options.primary = true;
if (!options.name) options.name = '_id';
// create and register a new column metadata
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'objectId',
options: options,
} as ColumnMetadataArgs);
};
}
@@ -0,0 +1,88 @@
import { getMetadataArgsStorage } from '../../globals';
import { ColumnTypeUndefinedError } from '../../error/ColumnTypeUndefinedError';
import { PrimaryColumnCannotBeNullableError } from '../../error/PrimaryColumnCannotBeNullableError';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { GeneratedMetadataArgs } from '../../metadata-args/GeneratedMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
import { ColumnType } from '../../driver/types/ColumnTypes';
/**
* Describes all primary key column's options.
* If specified, the nullable field must be set to false.
*/
export type PrimaryColumnOptions = ColumnOptions & { nullable?: false };
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
* Primary columns also creates a PRIMARY KEY for this column in a db.
*/
export function PrimaryColumn(options?: PrimaryColumnOptions): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
* Primary columns also creates a PRIMARY KEY for this column in a db.
*/
export function PrimaryColumn(type?: ColumnType, options?: PrimaryColumnOptions): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
* Primary columns also creates a PRIMARY KEY for this column in a db.
*/
export function PrimaryColumn(
typeOrOptions?: ColumnType | PrimaryColumnOptions,
options?: PrimaryColumnOptions,
): PropertyDecorator {
return function (object: Object, propertyName: string) {
// normalize parameters
let type: ColumnType | undefined;
if (
typeof typeOrOptions === 'string' ||
typeOrOptions === String ||
typeOrOptions === Boolean ||
typeOrOptions === Number
) {
type = typeOrOptions as ColumnType;
} else {
options = Object.assign({}, <PrimaryColumnOptions>typeOrOptions);
}
if (!options) options = {} as PrimaryColumnOptions;
// if type is not given explicitly then try to guess it
const reflectMetadataType =
Reflect && (Reflect as any).getMetadata
? (Reflect as any).getMetadata('design:type', object, propertyName)
: undefined;
if (!type && reflectMetadataType) type = reflectMetadataType;
// check if there is no type in column options then set type from first function argument, or guessed one
if (!options.type && type) options.type = type;
// if we still don't have a type then we need to give error to user that type is required
if (!options.type) throw new ColumnTypeUndefinedError(object, propertyName);
// check if column is not nullable, because we cannot allow a primary key to be nullable
if (options.nullable) throw new PrimaryColumnCannotBeNullableError(object, propertyName);
// explicitly set a primary to column options
options.primary = true;
// create and register a new column metadata
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'regular',
options: options,
} as ColumnMetadataArgs);
if (options.generated) {
getMetadataArgsStorage().generations.push({
target: object.constructor,
propertyName: propertyName,
strategy: typeof options.generated === 'string' ? options.generated : 'increment',
} as GeneratedMetadataArgs);
}
};
}
@@ -0,0 +1,115 @@
import { getMetadataArgsStorage } from '../../globals';
import { PrimaryGeneratedColumnNumericOptions } from '../options/PrimaryGeneratedColumnNumericOptions';
import { PrimaryGeneratedColumnUUIDOptions } from '../options/PrimaryGeneratedColumnUUIDOptions';
import { GeneratedMetadataArgs } from '../../metadata-args/GeneratedMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
import { PrimaryGeneratedColumnIdentityOptions } from '../options/PrimaryGeneratedColumnIdentityOptions';
import { ObjectUtils } from '../../util/ObjectUtils';
/**
* Column decorator is used to mark a specific class property as a table column.
*/
export function PrimaryGeneratedColumn(): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
*/
export function PrimaryGeneratedColumn(
options: PrimaryGeneratedColumnNumericOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
*/
export function PrimaryGeneratedColumn(
strategy: 'increment',
options?: PrimaryGeneratedColumnNumericOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
*/
export function PrimaryGeneratedColumn(
strategy: 'uuid',
options?: PrimaryGeneratedColumnUUIDOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
*/
export function PrimaryGeneratedColumn(
strategy: 'rowid',
options?: PrimaryGeneratedColumnUUIDOptions,
): PropertyDecorator;
export function PrimaryGeneratedColumn(
strategy: 'identity',
options?: PrimaryGeneratedColumnIdentityOptions,
): PropertyDecorator;
/**
* Column decorator is used to mark a specific class property as a table column.
* Only properties decorated with this decorator will be persisted to the database when entity be saved.
* This column creates an integer PRIMARY COLUMN with generated set to true.
*/
export function PrimaryGeneratedColumn(
strategyOrOptions?:
| 'increment'
| 'uuid'
| 'rowid'
| 'identity'
| PrimaryGeneratedColumnNumericOptions
| PrimaryGeneratedColumnUUIDOptions
| PrimaryGeneratedColumnIdentityOptions,
maybeOptions?:
| PrimaryGeneratedColumnNumericOptions
| PrimaryGeneratedColumnUUIDOptions
| PrimaryGeneratedColumnIdentityOptions,
): PropertyDecorator {
// normalize parameters
const options: ColumnOptions = {};
let strategy: 'increment' | 'uuid' | 'rowid' | 'identity';
if (strategyOrOptions) {
if (typeof strategyOrOptions === 'string')
strategy = strategyOrOptions as 'increment' | 'uuid' | 'rowid' | 'identity';
if (ObjectUtils.isObject(strategyOrOptions)) {
strategy = 'increment';
Object.assign(options, strategyOrOptions);
}
} else {
strategy = 'increment';
}
if (ObjectUtils.isObject(maybeOptions)) Object.assign(options, maybeOptions);
return function (object: Object, propertyName: string) {
// if column type is not explicitly set then determine it based on generation strategy
if (!options.type) {
if (strategy === 'increment' || strategy === 'identity') {
options.type = Number;
} else if (strategy === 'uuid') {
options.type = 'uuid';
} else if (strategy === 'rowid') {
options.type = 'int';
}
}
// explicitly set a primary and generated to column options
options.primary = true;
// register column metadata args
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'regular',
options: options,
});
// register generated metadata args
getMetadataArgsStorage().generations.push({
target: object.constructor,
propertyName: propertyName,
strategy: strategy,
} as GeneratedMetadataArgs);
};
}
@@ -0,0 +1,18 @@
import { getMetadataArgsStorage } from '../../globals';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
/**
* This column will store an update date of the updated object.
* This date is being updated each time you persist the object.
*/
export function UpdateDateColumn(options?: ColumnOptions): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'updateDate',
options: options ? options : {},
} as ColumnMetadataArgs);
};
}
@@ -0,0 +1,19 @@
import { getMetadataArgsStorage } from '../../globals';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
/**
* This column will store a number - version of the entity.
* Every time your entity will be persisted, this number will be increased by one -
* so you can organize visioning and update strategies of your entity.
*/
export function VersionColumn(options?: ColumnOptions): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'version',
options: options || {},
} as ColumnMetadataArgs);
};
}
@@ -0,0 +1,17 @@
import { getMetadataArgsStorage } from '../../globals';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { ViewColumnOptions } from '../options/ViewColumnOptions';
/**
* ViewColumn decorator is used to mark a specific class property as a view column.
*/
export function ViewColumn(options?: ViewColumnOptions): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'regular',
options: options || {},
} as ColumnMetadataArgs);
};
}
@@ -0,0 +1,66 @@
import { ColumnType } from '../../driver/types/ColumnTypes';
import { ColumnTypeUndefinedError } from '../../error';
import { getMetadataArgsStorage } from '../../globals';
import { ColumnMetadataArgs } from '../../metadata-args/ColumnMetadataArgs';
import { VirtualColumnOptions } from '../options/VirtualColumnOptions';
/**
* VirtualColumn decorator is used to mark a specific class property as a Virtual column.
*/
export function VirtualColumn(options: VirtualColumnOptions): PropertyDecorator;
/**
* VirtualColumn decorator is used to mark a specific class property as a Virtual column.
*/
export function VirtualColumn(
typeOrOptions: ColumnType,
options: VirtualColumnOptions,
): PropertyDecorator;
/**
* VirtualColumn decorator is used to mark a specific class property as a Virtual column.
*/
export function VirtualColumn(
typeOrOptions?: ColumnType | VirtualColumnOptions,
options?: VirtualColumnOptions,
): PropertyDecorator {
return function (object: Object, propertyName: string) {
// normalize parameters
let type: ColumnType | undefined;
if (typeof typeOrOptions === 'string') {
type = <ColumnType>typeOrOptions;
} else {
options = <VirtualColumnOptions>typeOrOptions;
type = options.type;
}
if (!options?.query) {
throw new Error('Column options must be defined for calculated columns.');
}
// if type is not given explicitly then try to guess it
const reflectMetadataType =
Reflect && (Reflect as any).getMetadata
? (Reflect as any).getMetadata('design:type', object, propertyName)
: undefined;
if (!type && reflectMetadataType)
// if type is not given explicitly then try to guess it
type = reflectMetadataType;
// check if there is no type in column options then set type from first function argument, or guessed one
if (type) options.type = type;
// specify HSTORE type if column is HSTORE
if (options.type === 'hstore' && !options.hstoreType)
options.hstoreType = reflectMetadataType === Object ? 'object' : 'string';
// if we still don't have a type then we need to give error to user that type is required
if (!options.type) throw new ColumnTypeUndefinedError(object, propertyName);
getMetadataArgsStorage().columns.push({
target: object.constructor,
propertyName: propertyName,
mode: 'virtual-property',
options: options || {},
} as ColumnMetadataArgs);
};
}
@@ -0,0 +1,44 @@
import { getMetadataArgsStorage } from '../../globals';
import { TableMetadataArgs } from '../../metadata-args/TableMetadataArgs';
import { ViewEntityOptions } from '../options/ViewEntityOptions';
import { ObjectUtils } from '../../util/ObjectUtils';
/**
* This decorator is used to mark classes that will be an entity view.
* Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
*/
export function ViewEntity(options?: ViewEntityOptions): ClassDecorator;
/**
* This decorator is used to mark classes that will be an entity view.
* Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
*/
export function ViewEntity(name?: string, options?: ViewEntityOptions): ClassDecorator;
/**
* This decorator is used to mark classes that will be an entity view.
* Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
*/
export function ViewEntity(
nameOrOptions?: string | ViewEntityOptions,
maybeOptions?: ViewEntityOptions,
): ClassDecorator {
const options =
(ObjectUtils.isObject(nameOrOptions) ? (nameOrOptions as ViewEntityOptions) : maybeOptions) ||
{};
const name = typeof nameOrOptions === 'string' ? nameOrOptions : options.name;
return function (target: Function) {
getMetadataArgsStorage().tables.push({
target: target,
name: name,
expression: options.expression,
dependsOn: options.dependsOn ? new Set(options.dependsOn) : undefined,
type: 'view',
database: options.database ? options.database : undefined,
schema: options.schema ? options.schema : undefined,
synchronize: options.synchronize === false ? false : true,
materialized: !!options.materialized,
} as TableMetadataArgs);
};
}
@@ -0,0 +1,24 @@
import { getMetadataArgsStorage } from '../../globals';
import { TableMetadataArgs } from '../../metadata-args/TableMetadataArgs';
import { DiscriminatorValueMetadataArgs } from '../../metadata-args/DiscriminatorValueMetadataArgs';
/**
* Special type of the table used in the single-table inherited tables.
*/
export function ChildEntity(discriminatorValue?: any): ClassDecorator {
return function (target: Function) {
// register a table metadata
getMetadataArgsStorage().tables.push({
target: target,
type: 'entity-child',
} as TableMetadataArgs);
// register discriminator value if it was provided
if (typeof discriminatorValue !== 'undefined') {
getMetadataArgsStorage().discriminatorValues.push({
target: target,
value: discriminatorValue,
} as DiscriminatorValueMetadataArgs);
}
};
}
@@ -0,0 +1,44 @@
import { getMetadataArgsStorage } from '../../globals';
import { TableMetadataArgs } from '../../metadata-args/TableMetadataArgs';
import { EntityOptions } from '../options/EntityOptions';
import { ObjectUtils } from '../../util/ObjectUtils';
/**
* This decorator is used to mark classes that will be an entity (table or document depend on database type).
* Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
*/
export function Entity(options?: EntityOptions): ClassDecorator;
/**
* This decorator is used to mark classes that will be an entity (table or document depend on database type).
* Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
*/
export function Entity(name?: string, options?: EntityOptions): ClassDecorator;
/**
* This decorator is used to mark classes that will be an entity (table or document depend on database type).
* Database schema will be created for all classes decorated with it, and Repository can be retrieved and used for it.
*/
export function Entity(
nameOrOptions?: string | EntityOptions,
maybeOptions?: EntityOptions,
): ClassDecorator {
const options =
(ObjectUtils.isObject(nameOrOptions) ? (nameOrOptions as EntityOptions) : maybeOptions) || {};
const name = typeof nameOrOptions === 'string' ? nameOrOptions : options.name;
return function (target) {
getMetadataArgsStorage().tables.push({
target: target,
name: name,
type: 'regular',
orderBy: options.orderBy ? options.orderBy : undefined,
engine: options.engine ? options.engine : undefined,
database: options.database ? options.database : undefined,
schema: options.schema ? options.schema : undefined,
synchronize: options.synchronize,
withoutRowid: options.withoutRowid,
comment: options.comment ? options.comment : undefined,
} as TableMetadataArgs);
};
}
@@ -0,0 +1,24 @@
import { getMetadataArgsStorage } from '../../globals';
import { InheritanceMetadataArgs } from '../../metadata-args/InheritanceMetadataArgs';
import { ColumnOptions } from '../options/ColumnOptions';
/**
* Sets for entity to use table inheritance pattern.
*/
export function TableInheritance(options?: {
pattern?: 'STI' /*|"CTI"*/;
column?: string | ColumnOptions;
}): ClassDecorator {
return function (target: Function) {
getMetadataArgsStorage().inheritances.push({
target: target,
pattern: options && options.pattern ? options.pattern : 'STI',
column:
options && options.column
? typeof options.column === 'string'
? { name: options.column }
: options.column
: undefined,
} as InheritanceMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied after this entity insertion.
*/
export function AfterInsert(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_INSERT,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied after entity is loaded.
*/
export function AfterLoad(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_LOAD,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied before this entity soft removal.
*/
export function AfterRecover(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_RECOVER,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied after this entity removal.
*/
export function AfterRemove(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_REMOVE,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied before this entity soft removal.
*/
export function AfterSoftRemove(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_SOFT_REMOVE,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied after this entity update.
*/
export function AfterUpdate(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.AFTER_UPDATE,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied before this entity insertion.
*/
export function BeforeInsert(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.BEFORE_INSERT,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied before this entity soft removal.
*/
export function BeforeRecover(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.BEFORE_RECOVER,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied before this entity removal.
*/
export function BeforeRemove(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.BEFORE_REMOVE,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied before this entity soft removal.
*/
export function BeforeSoftRemove(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.BEFORE_SOFT_REMOVE,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,16 @@
import { getMetadataArgsStorage } from '../../globals';
import { EventListenerTypes } from '../../metadata/types/EventListenerTypes';
import { EntityListenerMetadataArgs } from '../../metadata-args/EntityListenerMetadataArgs';
/**
* Calls a method on which this decorator is applied before this entity update.
*/
export function BeforeUpdate(): PropertyDecorator {
return function (object: Object, propertyName: string) {
getMetadataArgsStorage().entityListeners.push({
target: object.constructor,
propertyName: propertyName,
type: EventListenerTypes.BEFORE_UPDATE,
} as EntityListenerMetadataArgs);
};
}
@@ -0,0 +1,14 @@
import { getMetadataArgsStorage } from '../../globals';
import { EntitySubscriberMetadataArgs } from '../../metadata-args/EntitySubscriberMetadataArgs';
/**
* Classes decorated with this decorator will listen to ORM events and their methods will be triggered when event
* occurs. Those classes must implement EventSubscriberInterface interface.
*/
export function EventSubscriber(): ClassDecorator {
return function (target: Function) {
getMetadataArgsStorage().entitySubscribers.push({
target: target,
} as EntitySubscriberMetadataArgs);
};
}
@@ -0,0 +1,68 @@
import { ValueTransformer } from './ValueTransformer';
/**
* Column options specific to all column types.
*/
export interface ColumnCommonOptions {
/**
* Indicates if column is always selected by QueryBuilder and find operations.
* Default value is "true".
*/
select?: boolean;
/**
* Column name in the database.
*/
name?: string;
/**
* Indicates if this column is a primary key.
* Same can be achieved when @PrimaryColumn decorator is used.
*/
primary?: boolean;
/**
* Specifies if this column will use auto increment (sequence, generated identity, rowid).
* Note that in some databases only one column in entity can be marked as generated, and it must be a primary column.
*/
generated?: boolean | 'increment' | 'uuid' | 'rowid' | 'identity';
/**
* Specifies if column's value must be unique or not.
*/
unique?: boolean;
/**
* Indicates if column's value can be set to NULL.
*/
nullable?: boolean;
/**
* Default database value.
* Note that default value is not supported when column type is 'json' of mysql.
*/
default?: any;
/**
* ON UPDATE trigger. Works only for MySQL.
*/
onUpdate?: string;
/**
* Column comment. Not supported by all database types.
*/
comment?: string;
/**
* Indicates if this column is an array.
* Can be simply set to true or array length can be specified.
* Supported only by postgres.
*/
array?: boolean;
/**
* Specifies a value transformer that is to be used to (un)marshal
* this column when reading or writing to the database.
*/
transformer?: ValueTransformer | ValueTransformer[];
}
@@ -0,0 +1,17 @@
/**
* Column options specific to embedded column.
*/
export interface ColumnEmbeddedOptions {
/**
* Embedded column prefix.
* If set to empty string or false, then prefix is not set at all.
*/
prefix?: string | boolean;
/**
* Indicates if this embedded is in array mode.
*
* This option works only in mongodb.
*/
array?: boolean;
}
@@ -0,0 +1,13 @@
/**
* Column options for enum-typed columns.
*/
export interface ColumnEnumOptions {
/**
* Array of possible enumerated values.
*/
enum?: any[] | Object;
/**
* Exact name of enum
*/
enumName?: string;
}
@@ -0,0 +1,10 @@
/**
* Column options for enum-typed columns.
*/
export interface ColumnHstoreOptions {
/**
* Return type of HSTORE column.
* Returns value as string or as object.
*/
hstoreType?: string;
}
@@ -0,0 +1,27 @@
/**
* Options for numeric column types where user can specify scale and precision.
*/
export interface ColumnNumericOptions {
/**
* The precision for a decimal (exact numeric) column (applies only for decimal column), which is the maximum
* number of digits that are stored for the values.
*/
precision?: number;
/**
* The scale for a decimal (exact numeric) column (applies only for decimal column), which represents the number
* of digits to the right of the decimal point and must not be greater than precision.
*/
scale?: number;
/**
* Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
* If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to the column
*/
zerofill?: boolean;
/**
* Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
*/
unsigned?: boolean;
}
@@ -0,0 +1,190 @@
import { ColumnType } from '../../driver/types/ColumnTypes';
import { ValueTransformer } from './ValueTransformer';
import { ColumnCommonOptions } from './ColumnCommonOptions';
/**
* Describes all column's options.
*/
export interface ColumnOptions extends ColumnCommonOptions {
/**
* Column type. Must be one of the value from the ColumnTypes class.
*/
type?: ColumnType;
/**
* Column name in the database.
*/
name?: string;
/**
* Column type's length. Used only on some column types.
* For example type = "string" and length = "100" means that ORM will create a column with type varchar(100).
*/
length?: string | number;
/**
* Column type's display width. Used only on some column types in MySQL.
* For example, INT(4) specifies an INT with a display width of four digits.
*/
width?: number;
/**
* Indicates if column's value can be set to NULL.
* Default value is "false".
*/
nullable?: boolean;
/**
* Indicates if column value is not updated by "save" operation.
* It means you'll be able to write this value only when you first time insert the object.
* Default value is "false".
*
* @deprecated Please use the `update` option instead. Careful, it takes
* the opposite value to readonly.
*
*/
readonly?: boolean;
/**
* Indicates if column value is updated by "save" operation.
* If false, you'll be able to write this value only when you first time insert the object.
* Default value is "true".
*/
update?: boolean;
/**
* Indicates if column is always selected by QueryBuilder and find operations.
* Default value is "true".
*/
select?: boolean;
/**
* Indicates if column is inserted by default.
* Default value is "true".
*/
insert?: boolean;
/**
* Default database value.
*/
default?: any;
/**
* ON UPDATE trigger. Works only for MySQL.
*/
onUpdate?: string;
/**
* Indicates if this column is a primary key.
* Same can be achieved when @PrimaryColumn decorator is used.
*/
primary?: boolean;
/**
* Specifies if column's value must be unique or not.
*/
unique?: boolean;
/**
* Column comment. Not supported by all database types.
*/
comment?: string;
/**
* The precision for a decimal (exact numeric) column (applies only for decimal column), which is the maximum
* number of digits that are stored for the values.
*/
precision?: number | null;
/**
* The scale for a decimal (exact numeric) column (applies only for decimal column), which represents the number
* of digits to the right of the decimal point and must not be greater than precision.
*/
scale?: number;
/**
* Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
* If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to this column
*/
zerofill?: boolean;
/**
* Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
*/
unsigned?: boolean;
/**
* Defines a column character set.
* Not supported by all database types.
*/
charset?: string;
/**
* Defines a column collation.
*/
collation?: string;
/**
* Array of possible enumerated values.
*/
enum?: (string | number)[] | Object;
/**
* Exact name of enum
*/
enumName?: string;
/**
* If this column is primary key then this specifies the name for it.
*/
primaryKeyConstraintName?: string;
/**
* If this column is foreign key then this specifies the name for it.
*/
foreignKeyConstraintName?: string;
/**
* Generated column expression.
*/
asExpression?: string;
/**
* Generated column type.
*/
generatedType?: 'VIRTUAL' | 'STORED';
/**
* Identity column type. Supports only in Postgres 10+.
*/
generatedIdentity?: 'ALWAYS' | 'BY DEFAULT';
/**
* Return type of HSTORE column.
* Returns value as string or as object.
*/
hstoreType?: 'object' | 'string';
/**
* Indicates if this column is an array.
* Can be simply set to true or array length can be specified.
* Supported only by postgres.
*/
array?: boolean;
/**
* Specifies a value transformer that is to be used to (un)marshal
* this column when reading or writing to the database.
*/
transformer?: ValueTransformer | ValueTransformer[];
/**
* Spatial Feature Type (Geometry, Point, Polygon, etc.)
*/
spatialFeatureType?: string;
/**
* SRID (Spatial Reference ID (EPSG code))
*/
srid?: number;
}
@@ -0,0 +1,10 @@
/**
* Options for columns that can define a length of the column type.
*/
export interface ColumnWithLengthOptions {
/**
* Column type's length.
* For example type = "varchar" and length = "100" means ORM will create a column with type varchar(100).
*/
length?: string | number;
}
@@ -0,0 +1,21 @@
/**
* Options for columns that can define a length of the column type.
*/
export interface ColumnWithWidthOptions {
/**
* Column type's display width. Used only on some column types in MySQL.
* For example, INT(4) specifies an INT with a display width of four digits.
*/
width?: number;
/**
* Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
* If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to this column
*/
zerofill?: boolean;
/**
* Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
*/
unsigned?: boolean;
}
@@ -0,0 +1,54 @@
import { OrderByCondition } from '../../find-options/OrderByCondition';
/**
* Describes all entity's options.
*/
export interface EntityOptions {
/**
* Table name.
* If not specified then naming strategy will generate table name from entity name.
*/
name?: string;
/**
* Specifies a default order by used for queries from this table when no explicit order by is specified.
*/
orderBy?: OrderByCondition | ((object: any) => OrderByCondition | any);
/**
* Table's database engine type (like "InnoDB", "MyISAM", etc).
* It is used only during table creation.
* If you update this value and table is already created, it will not change table's engine type.
* Note that not all databases support this option.
*/
engine?: string;
/**
* Database name. Used in Mysql and Sql Server.
*/
database?: string;
/**
* Schema name. Used in Postgres and Sql Server.
*/
schema?: string;
/**
* Indicates if schema synchronization is enabled or disabled for this entity.
* If it will be set to false then schema sync will and migrations ignore this entity.
* By default schema synchronization is enabled for all entities.
*/
synchronize?: boolean;
/**
* If set to 'true' this option disables Sqlite's default behaviour of secretly creating
* an integer primary key column named 'rowid' on table creation.
* @see https://www.sqlite.org/withoutrowid.html.
*/
withoutRowid?: boolean;
/**
* Table comment. Not supported by all database types.
*/
comment?: string;
}
@@ -0,0 +1,66 @@
/**
* Describes all index options.
*/
export interface IndexOptions {
/**
* Indicates if this composite index must be unique or not.
*/
unique?: boolean;
/**
* The SPATIAL modifier indexes the entire column and does not allow indexed columns to contain NULL values.
* Works only in MySQL and PostgreSQL.
*/
spatial?: boolean;
/**
* The FULLTEXT modifier indexes the entire column and does not allow prefixing.
* Works only in MySQL.
*/
fulltext?: boolean;
/**
* NULL_FILTERED indexes are particularly useful for indexing sparse columns, where most rows contain a NULL value.
* In these cases, the NULL_FILTERED index can be considerably smaller and more efficient to maintain than
* a normal index that includes NULL values.
*
* Works only in Spanner.
*/
nullFiltered?: boolean;
/**
* Fulltext parser.
* Works only in MySQL.
*/
parser?: string;
/**
* Index filter condition.
*/
where?: string;
/**
* If true, the index only references documents with the specified field.
* These indexes use less space but behave differently in some situations (particularly sorts).
* This option is only supported for mongodb database.
*/
sparse?: boolean;
/**
* Builds the index in the background so that building an index an does not block other database activities.
* This option is only supported for mongodb database.
*/
background?: boolean;
/**
* Create the index using the CONCURRENTLY modifier
* Works only in postgres.
*/
concurrent?: boolean;
/**
* Specifies a time to live, in seconds.
* This option is only supported for mongodb database.
*/
expireAfterSeconds?: number;
}
@@ -0,0 +1,19 @@
/**
* Describes join column options.
*/
export interface JoinColumnOptions {
/**
* Name of the column.
*/
name?: string;
/**
* Name of the column in the entity to which this column is referenced.
*/
referencedColumnName?: string; // TODO rename to referencedColumn
/**
* Name of the foreign key constraint.
*/
foreignKeyConstraintName?: string;
}
@@ -0,0 +1,41 @@
import { JoinColumnOptions } from './JoinColumnOptions';
/**
* Describes all join table with multiple column options.
*/
export interface JoinTableMultipleColumnsOptions {
/**
* Name of the table that will be created to store values of the both tables (join table).
* By default is auto generated.
*/
name?: string;
/**
* First column of the join table.
*/
joinColumns?: JoinColumnOptions[];
/**
* Second (inverse) column of the join table.
*/
inverseJoinColumns?: JoinColumnOptions[];
/**
* Database where join table will be created.
* Works only in some databases (like mysql).
*/
database?: string;
/**
* Schema where join table will be created.
* Works only in some databases (like postgres).
*/
schema?: string;
/**
* Indicates if schema synchronization is enabled or disabled junction table.
* If it will be set to false then schema sync will and migrations ignores junction table.
* By default schema synchronization is enabled.
*/
readonly synchronize?: boolean;
}
@@ -0,0 +1,41 @@
import { JoinColumnOptions } from './JoinColumnOptions';
/**
* Describes join table options.
*/
export interface JoinTableOptions {
/**
* Name of the table that will be created to store values of the both tables (join table).
* By default is auto generated.
*/
name?: string;
/**
* First column of the join table.
*/
joinColumn?: JoinColumnOptions;
/**
* Second (inverse) column of the join table.
*/
inverseJoinColumn?: JoinColumnOptions;
/**
* Database where join table will be created.
* Works only in some databases (like mysql).
*/
database?: string;
/**
* Schema where join table will be created.
* Works only in some databases (like postgres).
*/
schema?: string;
/**
* Indicates if schema synchronization is enabled or disabled junction table.
* If it will be set to false then schema sync will and migrations ignores junction table.
* By default schema synchronization is enabled.
*/
synchronize?: boolean;
}
@@ -0,0 +1,31 @@
import { PrimaryGeneratedColumnType } from '../../driver/types/ColumnTypes';
/**
* Describes all options for PrimaryGeneratedColumn decorator with identity generation strategy.
*/
export interface PrimaryGeneratedColumnIdentityOptions {
/**
* Column type. Must be one of the value from the ColumnTypes class.
*/
type?: PrimaryGeneratedColumnType;
/**
* Column name in the database.
*/
name?: string;
/**
* Column comment. Not supported by all database types.
*/
comment?: string;
/**
* Identity column type. Supports only in Postgres 10+.
*/
generatedIdentity?: 'ALWAYS' | 'BY DEFAULT';
/**
* Name of the primary key constraint.
*/
primaryKeyConstraintName?: string;
}
@@ -0,0 +1,37 @@
import { PrimaryGeneratedColumnType } from '../../driver/types/ColumnTypes';
/**
* Describes all options for PrimaryGeneratedColumn decorator with numeric generation strategy.
*/
export interface PrimaryGeneratedColumnNumericOptions {
/**
* Column type. Must be one of the value from the ColumnTypes class.
*/
type?: PrimaryGeneratedColumnType;
/**
* Column name in the database.
*/
name?: string;
/**
* Column comment. Not supported by all database types.
*/
comment?: string;
/**
* Puts ZEROFILL attribute on to numeric column. Works only for MySQL.
* If you specify ZEROFILL for a numeric column, MySQL automatically adds the UNSIGNED attribute to the column
*/
zerofill?: boolean;
/**
* Puts UNSIGNED attribute on to numeric column. Works only for MySQL.
*/
unsigned?: boolean;
/**
* Name of the primary key constraint.
*/
primaryKeyConstraintName?: string;
}
@@ -0,0 +1,19 @@
/**
* Describes all options for PrimaryGeneratedColumn decorator with numeric uuid strategy.
*/
export interface PrimaryGeneratedColumnUUIDOptions {
/**
* Column name in the database.
*/
name?: string;
/**
* Column comment. Not supported by all database types.
*/
comment?: string;
/**
* Name of the primary key constraint.
*/
primaryKeyConstraintName?: string;
}

Some files were not shown because too many files have changed in this diff Show More