From 86a347d1f9416bd4422bcdad30902e387851355a Mon Sep 17 00:00:00 2001 From: Umed Khudoiberdiev Date: Sun, 16 Oct 2016 14:56:41 +0500 Subject: [PATCH] refactored readme stuff and added more contributing docs --- .gitignore | 3 +- CHANGELOG.md | 12 + CONTRIBUTING.md | 119 +++ DEVELOPER.md | 95 ++ LICENCE | 21 + README.md | 1157 ++++++++++++--------- docs/command-line-tools.md | 12 - docs/connection-and-connection-options.md | 159 --- docs/databases-and-drivers.md | 36 - docs/decorators-reference.md | 196 ---- docs/entity-manager.md | 70 -- docs/indices-and-keys.md | 3 - docs/naming-strategies.md | 54 - docs/query-builder.md | 148 --- docs/relations.md | 41 - docs/repository.md | 67 -- docs/subscribers-and-entity-listeners.md | 127 --- docs/table-columns.md | 173 --- docs/tables-and-table-inheritance.md | 57 - docs/updating-database-schema.md | 17 - gulpfile.ts | 4 +- ormconfig.json.dist | 14 + package.json | 2 +- sample/sample2-one-to-one/app.ts | 4 +- tsconfig.json | 6 +- 25 files changed, 959 insertions(+), 1638 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 DEVELOPER.md create mode 100644 LICENCE delete mode 100644 docs/command-line-tools.md delete mode 100644 docs/connection-and-connection-options.md delete mode 100644 docs/databases-and-drivers.md delete mode 100644 docs/decorators-reference.md delete mode 100644 docs/entity-manager.md delete mode 100644 docs/indices-and-keys.md delete mode 100644 docs/naming-strategies.md delete mode 100644 docs/query-builder.md delete mode 100644 docs/relations.md delete mode 100644 docs/repository.md delete mode 100644 docs/subscribers-and-entity-listeners.md delete mode 100644 docs/table-columns.md delete mode 100644 docs/tables-and-table-inheritance.md delete mode 100644 docs/updating-database-schema.md create mode 100644 ormconfig.json.dist diff --git a/.gitignore b/.gitignore index e7637263d3..0186875d4b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ build/ coverage/ node_modules/ -typings/ npm-debug.log -config/parameters.json \ No newline at end of file +ormconfig.json \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..b07c3971f6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# 0.0.2 (in development) + +* lot of API refactorings +* complete support TypeScript 2 +* optimized schema creation +* command line tools +* multiple drivers support +* multiple bugfixes + +# 0.0.1 + +* first stable version, works with TypeScript 1.x \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..22faf849f4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,119 @@ +# Contributing to TypeORM + +We would love for you to contribute to TypeORM and help make it even better than it is today! +As a contributor, here are the guidelines we would like you to follow: + + - [Question or Problem?](#question) + - [Issues and Bugs](#issue) + - [Feature Requests](#feature) + - [Submission Guidelines](#submit) + +## Got a Question or Problem? + +There are several ways how you can ask your question: + +* You can create a question on [StackOverflow](stackoverflow.com/questions/tagged/typeorm) where the questions should be tagged with tag `typeorm`. +* You can create issue on [github](https://github.com/typeorm/typeorm/issues) +* You can write your questions in our [gitter channel](https://gitter.im/pleerock/typeorm) +* If you have a Skype then try to find me there (`Umed Khudoiberdiev`) + +Prefered way if you create your question on StackOverflow, or create a github issue. + +## Found a Bug? + +If you find a bug in the source code, you can help us by [submitting an issue](#submit-issue) to our +[GitHub Repository](https://github.com/typeorm/typeorm). +Even better, you can [submit a Pull Request](#submit-pr) with a fix. + +## Missing a Feature? + +You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub +Repository. If you would like to *implement* a new feature, please submit an issue with +a proposal for your work first, to be sure that we can use it. +Please consider what kind of change it is: + +* For a **Major Feature**, first open an issue and outline your proposal so that it can be +discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, +and help you to craft the change so that it is successfully accepted into the project. +* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). + +## Submission Guidelines + +### Submitting an Issue + +Before you submit an issue, please search the issue tracker, +maybe an issue for your problem already exists and the discussion might inform you of workarounds readily available. + +We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. + In order to reproduce bugs we ask you to provide a minimal code snippet that shows a reproduction of the problem. + +You can file new issues by filling out our [new issue form](https://github.com/typeorm/typeorm/issues/new). + +### Submitting a Pull Request (PR) +Before you submit your Pull Request (PR) consider the following guidelines: + +* Search [GitHub](https://github.com/typeorm/typeorm/pulls) for an open or closed PR + that relates to your submission. You don't want to duplicate effort. +* Make your changes in a new git branch: + + ```shell + git checkout -b my-fix-branch master + ``` + +* Create your patch, **including appropriate test cases**. Without tests your PR will not be accepted. +* Follow our [Coding Rules](#rules). +* Run the full TypeORM test suite, as described in the [developer documentation](DEVELOPER.md), and ensure that all tests pass. +* Commit your changes using a descriptive commit message + + ```shell + git commit -a + ``` + +* Push your branch to GitHub: + + ```shell + git push origin my-fix-branch + ``` + +* In GitHub, send a pull request to `typeorm:master`. +* If we suggest changes then: + * Make the required updates. + * Re-run the TypeORM test suites to ensure tests are still passing. + * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): + + ```shell + git rebase master -i + git push -f + ``` + +That's it! Thank you for your contribution! + +#### After your pull request is merged + +After your pull request is merged, you can safely delete your branch and pull the changes +from the main (upstream) repository: + +* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: + + ```shell + git push origin --delete my-fix-branch + ``` + +* Check out the master branch: + + ```shell + git checkout master -f + ``` + +* Delete the local branch: + + ```shell + git branch -D my-fix-branch + ``` + +* Update your master with the latest upstream version: + + ```shell + git pull --ff upstream master + ``` + diff --git a/DEVELOPER.md b/DEVELOPER.md new file mode 100644 index 0000000000..ecc9ccc3de --- /dev/null +++ b/DEVELOPER.md @@ -0,0 +1,95 @@ +# Building and Testing TypeORM + +This document describes how to set up your development environment and run TypeORM test cases. + +* [Prerequisite Software](#prerequisite-software) +* [Getting the Sources](#getting-the-sources) +* [Installing NPM Modules](#installing-npm-modules) +* [Building](#building) +* [Running Tests Locally](#running-tests-locally) + +See the [contribution guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md) +if you'd like to contribute to Angular. + +## Prerequisite Software + +Before you can build and test TypeORM, you must install and configure the +following products on your development machine: + +* [Git](http://git-scm.com) and/or the **GitHub app** (for [Mac](http://mac.github.com) or + [Windows](http://windows.github.com)); [GitHub's Guide to Installing + Git](https://help.github.com/articles/set-up-git) is a good source of information. + +* [Node.js](http://nodejs.org), (better to install latest version) which is used to run a development web server, + run tests, and generate distributable files. + Depending on your system, you can install Node either from source or as a pre-packaged bundle. +* [Mysql](https://www.mysql.com/) is required to run tests on this platform +* [MariaDB](https://mariadb.com/) is required to run tests on this platform +* [Postgres](https://www.postgresql.org/) is required to run tests on this platform +* [Oracle](https://www.oracle.com/database/index.html) is required to run tests on this platform +* [Microsoft SQL Server](https://www.microsoft.com/en-us/cloud-platform/sql-server) is required to run tests on this platform + +## Getting the Sources + +Fork and clone the repository: + +1. Login to your GitHub account or create one by following the instructions given [here](https://github.com/signup/free). +2. [Fork](http://help.github.com/forking) the [main TypeORM repository](https://github.com/typeorm/typeorm). +3. Clone your fork of the TypeORM repository and define an `upstream` remote pointing back to + the TypeORM repository that you forked in the first place. + +```shell +# Clone your GitHub repository: +git clone git@github.com:/typeorm.git + +# Go to the TypeORM directory: +cd typeorm + +# Add the main TyepORM repository as an upstream remote to your repository: +git remote add upstream https://github.com/typeorm/typeorm.git +``` +## Installing NPM Modules + +Install all TypeORM dependencies by running this command: + +```shell +npm install +``` + +During installation you may have some probelems with some dependencies. +For example to proper install oracle driver you need to follow all instructions from + [node-oracle documentation](https://github.com/oracle/node-oracledb). + +Also install these packages globally: + +* `npm install -g gulp` (you might need to prefix this command with `sudo`) +* `npm install -g typescript` (you might need to prefix this command with `sudo`) + +## Building + +To build a distribution package of TypeORM run: + +```shell +gulp package +``` + +This command will generate you a distribution package in the `build/package` directory. +You can link (or simply copy/paste) this directory into your project and test TypeORM there +(but make sure to keep all node_modules required by TypeORM). + +## Running Tests Locally + +Setup your environment configuration by copying `ormconfig.json.dist` into `ormconfig.json` and +replacing parameters with your own. + +Then run tests: + +```shell +gulp tests +``` + +You should execute test suites before submitting a PR to github. +All the tests are executed on our Continuous Integration infrastructure and a PR could only be merged once the tests pass. + + + diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000000..9fc329de94 --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2015-2016 Yakdu. 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. \ No newline at end of file diff --git a/README.md b/README.md index 9a8c96457b..e2fab67571 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,9 @@ [![Join the chat at https://gitter.im/pleerock/typeorm](https://badges.gitter.im/pleerock/typeorm.svg)](https://gitter.im/pleerock/typeorm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -> Note: docs are not always up to date because orm is in active development. -> Samples are more up to date, try to find your questions there. -> Otherwise create a github issue. -> -> Note: Current version of orm works with typescript >1.9. It means you need to install -> typescript@next to work with it. If you want to use older version of orm you can try -> to install typeorm@0.0.1 - -## What is TypeORM? +> ORM development is in alpha stage. Don't be frustrated with bugs if you face them. +If you notice bug or have something not working please report an issue, we'll try to fix it as soon as possible. +More documentation and features expected to be soon. Feel free to contribute. TypeORM is an [Object Relational Mapper](1) (ORM) for node.js written in Typescript that can be used with Typescript or Javascript (ES5, ES6, ES7). @@ -27,11 +21,11 @@ to javascript object's properties * create one-to-one, many-to-one, one-to-many, many-to-many relations between tables * and much more ... -TypeORM uses Data Mapper pattern, unlike all other javascript ORMs that +TypeORM uses Data Mapper pattern, unlike all other JavaScript ORMs that currently exist, which means you can write loosely coupled, scalable, maintainable applications with less problems. -The benefit of using ORM for the programmer is the ability to focus on +The benefit of using TypeORM for the programmer is the ability to focus on the business logic and worry about persistence only as a secondary problem. TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate.org/orm/) and [Doctrine](http://www.doctrine-project.org/). @@ -50,278 +44,545 @@ TypeORM is highly influenced by other ORMs, such as [Hibernate](http://hibernate * `require("reflect-metadata")` in your app's entry point (for example `app.ts`) -3. ES6 features are used, so you may want to install [es6-shim](https://github.com/paulmillr/es6-shim) if you are using older version of node.js: +3. Install database driver: - `npm install es6-shim --save` + * for **MySQL** or **MariaDB** + + `npm install mysql --save` + + * for **Postgres** + + `npm install pg --save` + + * for **SQLite** + + `npm install sqlite3 --save` + + * for **Microsoft SQL Server** + + `npm install mssql --save` + + * for **Oracle** + + `npm install oracledb --save` + + Install only one of them, depend on which database you use. + - and use it somewhere in the global place of your app, before the code where you start to use orm: +## Quick Start - * `require("es6-shim")` in your app's entry point (for example `app.ts`) +In TypeORM tables are created from Entities. +*Entity* is your model decorated by a `@Table` decorator. +You can get entities from the database and insert/update/remove them from there. +Let's say we have a model `entity/Photo.ts`: -4. Install database driver: +```typescript +export class Photo { + id: number; + name: string; + description: string; + fileName: string; + views: number; +} +```` + +### Creating entity - 3.1. Mysql +Now lets make it entity: - If you want to use ORM with mysql then install its driver: +```typescript +import {Table} from "typeorm"; - `npm install mysql --save` +@Table() +export class Photo { + id: number; + name: string; + description: string; + fileName: string; + views: number; + isPublished: boolean; +} +``` + +### Add table columns - 3.2. Postgres (still in development, don't use it yet) +Now we have a table, and each table consist of columns. +Let's add some columns. +You can make any property of your model a column by using a `@Column` decorator: - `npm install pg --save` +```typescript +import {Table, Column} from "typeorm"; + +@Table() +export class Photo { + + @Column() + id: number; - Right now only `mysql` and `postgres` databases are supported. Feel free to contribute and add support of new drivers. + @Column() + name: string; -## Example + @Column() + description: string; -Lets create a sample application - a photo album. + @Column() + fileName: string; -#### create Photo entity class + @Column() + views: number; -First we create a new file `Photo.ts` and put a class there: + @Column() + isPublished: boolean; +} +``` + +### Create a primary column + +Perfect. +Now ORM will generate us a photo table with all its properties as columns. +But there is one thing left. +Each entity must have a primary column. +This is requirement and you can't avoid it. +To make a column a primary you need to use `@PrimaryColumn` decorator. ```typescript -import {Table} from "typeorm"; -import {PrimaryColumn, Column} from "typeorm"; +import {Table, Column, PrimaryColumn} from "typeorm"; -@Table("photo") +@Table() export class Photo { - + + @PrimaryColumn() + id: number; + + @Column() + name: string; + + @Column() + description: string; + + @Column() + fileName: string; + + @Column() + views: number; + + @Column() + isPublished: boolean; +} +``` + +### Create auto-increment / generated / sequence / identity column + +Now, lets say you want to make your id column to be auto-generated (this is known as auto-increment / sequence / generated identity column). +To do that you need to change your column's type to integer and set a `{ generated: true }` in your primary column's options: + +```typescript +import {Table, Column, PrimaryColumn} from "typeorm"; + +@Table() +export class Photo { + @PrimaryColumn("int", { generated: true }) id: number; - + @Column() name: string; - + @Column() description: string; - + @Column() - filename: string; - + fileName: string; + + @Column() + views: number; + @Column() isPublished: boolean; - } ``` -Here, we are using three decorators: -* `@Table(tableName)` - tells ORM to create a new table in the database -for this class. We also specified a table name in the database. -* `@PrimaryColumn(columnType, columnOptions)` - tells ORM to create a table -column for the given class property and make it *PRIMARY KEY* column. We also - set `{ generated: true }` in column options, which makes our - primary column an *AUTO_INCREMENT*. -* `@Column(columnType, columnOptions)` - tells ORM to create a table -column for the given class property. +### Using `@PrimaryGeneratedColumn` decorator + +Now your photo's id will always be a generated, auto increment value. +Since this is a common task - to create a generated auto increment primary column, +there is a special decorator called `@PrimaryGeneratedColumn` to do the same. +Let's use it instead: -#### connect to the database and register Photo entity class in ORM +```typescript +import {Table, Column, PrimaryGeneratedColumn} from "typeorm"; -Now lets run bootstrap our application and connect to the database. Create -`app.ts`: +@Table() +export class Photo { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + name: string; + + @Column() + description: string; + + @Column() + fileName: string; + + @Column() + views: number; + + @Column() + isPublished: boolean; +} +``` + +### Custom column data types + +Next step, lets fix our data types. By default, string is mapped to a varchar(255)-like type (depend of database type). +Number is mapped to a float/double-like type (depend of database type). +We don't want all our columns to be limited varchars or excessive floats. +Lets setup correct data types: ```typescript -import {createConnection, CreateConnectionOptions} from "typeorm"; -import {Photo} from "./Photo"; +import {Table, Column, PrimaryGeneratedColumn} from "typeorm"; -const options: CreateConnectionOptions = { +@Table() +export class Photo { + + @PrimaryGeneratedColumn() + id: number; + + @Column({ + length: 500 + }) + name: string; + + @Column("text") + description: string; + + @Column() + fileName: string; + + @Column("int") + views: number; + + @Column() + isPublished: boolean; +} +``` + +### Creating connection with the database + +Now, when our entity is created, lets create `app.ts` file and setup our connection there: + +```typescript +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; + +createConnection({ driver: { - type: "mysql", // specify driver type here. Right now only "mysql" is supported - host: "localhost", // mysql host - port: 3306, // mysql port - username: "root", // mysql database username - password: "admin", // mysql database password - database: "test", // mysql database name - autoSchemaSync: true // if set to true, then database schema will be automatically created on each application start + type: "mysql", + host: "localhost", + port: 3306, + username: "root", + password: "admin", + database: "test" }, - entities: [Photo] // array of classes you want to create tables for (and work with them in the current connection) -}; + entities: [ + Photo + ], + autoSchemaSync: true, +}).then(connection => { + // here you can start to work with your entities +}); +``` -createConnection(options).then(connection => { +We are using mysql in this example, but you can use any other database. +To use another database simply change type in the driver options to the database type you are using: +mysql, mariadb, postgres, sqlite, mssql or oracle. +Also make sure to use your own host, port, username, password and database settings. - // at this point you are connected to the database, and you can - // perform queries +We added our Photo entity to the list of entities for this connection. +Each entity you are using in your connection must be listed here. -}).catch(error => console.log("Error during connection to the db: ", error)); -``` +Setting `autoSchemaSync` makes sure your entities will be synced with the database, every time you run the application. + +### Loading all entities from the directory + +Later, when we create more entities we need to add them to the entities in our configuration. +But this is not very convenient, and instead we can setup the whole directory, +where from all entities will be connected and used in our connection: -Now run your `app.ts`. ORM will automatically create a `photo` table in -the `test` database: +```typescript +import {createConnection} from "typeorm"; +createConnection({ + driver: { + type: "mysql", + host: "localhost", + port: 3306, + username: "root", + password: "admin", + database: "test" + }, + entities: [ + __dirname + "/entity/*.js" + ], + autoSchemaSync: true, +}).then(connection => { + // here you can start to work with your entities +}); ``` + +### Run the application + +Now you if run your `app.ts`, connection with database will be initialized and database table for your Photo will be created. + + +```shell +-------------+--------------+----------------------------+ | photo | +-------------+--------------+----------------------------+ | id | int(11) | PRIMARY KEY AUTO_INCREMENT | -| name | varchar(255) | | -| description | varchar(255) | | +| name | varchar(500) | | +| description | text | | | filename | varchar(255) | | +| views | int(11) | | | isPublished | boolean | | +-------------+--------------+----------------------------+ ``` + +Now you can run your `app.ts`, connection with database will be initialized, and database table for your Photo will be created. -#### inserting photo into the database +### Creating and inserting photo into the database -Now lets create a new Photo, and persist it to the database. +Now lets create a new photo to save it in the database: ```typescript -createConnection(options).then(connection => { +import {createConnection} from "typeorm"; + +createConnection(/*...*/).then(connection => { let photo = new Photo(); photo.name = "Me and Bears"; photo.description = "I am near polar bears"; - photo.filename = "photo-with-bears.jpg" + photo.filename = "photo-with-bears.jpg"; + photo.views = 1; photo.isPublished = true; - - let repository = connection.getRepository(Photo); - repository.persist(photo).then(photo => { - console.log("Photo has been persisted to the database."); - console.log("New photo id is ", photo.id); - }); + + connection.entityManager + .persist(photo) + .then(photo => { + console.log("Photo has been saved"); + }); + +}); +``` + +### Using async/await syntax + +Lets use latest TypeScript advantages and use async/await syntax instead: + +```typescript +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; + +createConnection(/*...*/).then(async connection => { + + let photo = new Photo(); + photo.name = "Me and Bears"; + photo.description = "I am near polar bears"; + photo.filename = "photo-with-bears.jpg"; + photo.views = 1; + photo.isPublished = true; + + await connection.entityManager.persist(photo); + console.log("Photo has been saved"); }); ``` -#### loading photos from the database +### Using Entity Manager -If you want to load photos from the database, you can use `repository.find*` -methods: +We just created a new photo and saved it in the database. +We used `EntityManager` to save it. +Using entity managers you can manipulate any entity in your app. +Now lets load our saved entity: ```typescript -// here we load one photo by id: -let photoId = 1; -let repository = connection.getRepository(Photo); -repository.findById(photoId).then(photo => { - console.log("Photo is loaded: ", photo); +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; + +createConnection(/*...*/).then(async connection => { + + /*...*/ + let savedPhotos = await connection.entityManager.find(Photo); + console.log("All photos from the db: ", savedPhotos); + }); +``` + +savedPhotos will be an array of Photo objects with the data loaded from the database. + +### Using Repositories + +Now lets refactor our code and use `Repository` instead of EntityManager. +Each entity has its own repository which handles all operations with its entity. +When you deal with entities a lot, Repositories are more convenient to use then EntityManager: + + +```typescript +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; + +createConnection(/*...*/).then(async connection => { + + let photo = new Photo(); + photo.name = "Me and Bears"; + photo.description = "I am near polar bears"; + photo.filename = "photo-with-bears.jpg"; + photo.views = 1; + photo.isPublished = true; + + let photoRepository = connection.getRepository(Photo); + + await photoRepository.persist(photo); + console.log("Photo has been saved"); + + let savedPhotos = await photoRepository.find(); + console.log("All photos from the db: ", savedPhotos); -// here we load one photo by name -repository.findOne({ name: "Me and Bears" }).then(photo => { - console.log("Photo is loaded: ", photo); }); +``` + +### Loading photos from the database + +Lets try more load operations using Repository: + +```typescript +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; + +createConnection(/*...*/).then(async connection => { + + /*...*/ + let allPhotos = await photoRepository.find(); + console.log("All photos from the db: ", allPhotos); + + let firstPhoto = await photoRepository.findOneById(1); + console.log("First photo from the db: ", firstPhoto); + + let meAndBearsPhoto = await photoRepository.findOne({ name: "Me and Bears" }); + console.log("Me and Bears photo from the db: ", meAndBearsPhoto); + + let allViewedPhotos = await photoRepository.find({ views: 1 }); + console.log("All viewed photos: ", allViewedPhotos); + + let allPublishedPhotos = await photoRepository.find({ isPublished: true }); + console.log("All published photos: ", allPublishedPhotos); + + let [allPhotos, photosCount] = await photoRepository.findAndCount(); + console.log("All photos: ", allPublishedPhotos); + console.log("Photos count: ", allPublishedPhotos); -// here we load all published photos -repository.find({ isPublished: true }).then(photos => { - console.log("Published photos are loaded: ", photos); }); ``` -#### updating photo in the database +### Updating photo in the database -If you want to update in the database a previously loaded photo, you -can use `repository.persist` method: +Now lets load a single photo from the database, update it and save it: ```typescript -// change previously loaded photo -photo.name = "Me and Bears and Penguins"; -photo.description = "I am near polar bears and penguins"; - -// call persist method to update a photo -let repository = connection.getRepository(Photo); -repository.persist(photo).then(photo => { - console.log("Photo is updated in the database: ", photo); +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; + +createConnection(/*...*/).then(async connection => { + + /*...*/ + let photoToUpdate = await photoRepository.findOneById(1); + photoToUpdate.name = "Me, my friends and polar bears"; + await photoRepository.persist(photoToUpdate); + }); ``` -#### removing photo from the database +Now photo with `id = 1` will be updated in the database. + +### Removing photo from the database + +Now let's remove our photo from the database: -If you want to remove a photo from the database, you can use -`repository.remove` method: ```typescript -let repository = connection.getRepository(Photo); -repository.remove(photo).then(() => { - console.log("Photo has been successfully removed."); +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; + +createConnection(/*...*/).then(async connection => { + + /*...*/ + let photoToRemove = await photoRepository.findOneById(1); + await photoRepository.remove(photoToRemove); + }); -``` +``` -#### creating a one-to-one relation +Now photo with `id = 1` will be removed from the database. -Lets create a one-to-one relation with another class. Lets create a new -class called `PhotoMetadata.ts` which will contain a `PhotoMetadata` class -which supposed to be contain our Photo's additional meta-information: +### creating a one-to-one relation + +Lets create a one-to-one relation with another class. +Lets create a new class called PhotoMetadata.ts which will contain a PhotoMetadata class which supposed to contain our photo's additional meta-information: ```typescript -import {Table} from "typeorm"; -import {PrimaryColumn, Column} from "typeorm"; -import {OneToOne, JoinColumn} from "typeorm"; +import {Table, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn} from "typeorm"; +import {Photo} from "./Photo"; -@Table("photo_metadata") +@Table() export class PhotoMetadata { - - @PrimaryColumn("int", { generated: true }) + + @PrimaryGeneratedColumn() id: number; - - @Column() + + @Column("int") height: number; - - @Column() + + @Column("int") width: number; - + @Column() - comment: string; - + orientation: string; + @Column() compressed: boolean; - + @Column() - orientation: string; - - @OneToOne(type => Photo, photo => photo.metadata) // note: we will create metadata property in the Photo class below + comment: string; + + @OneToOne(type => Photo) @JoinColumn() photo: Photo; } ``` + +Here, we are used a new decorator called `@OneToOne`. It allows to create one-to-one relations between two entities. +`type => Photo` is a function that returns the class of the entity with which we want to make our relation. +We are forced to use a function that returns a class, instead of using class directly, because of the language specifics. +We can also write it as a `() => Photo`, but we use `type => Photo as convention to increase code readability. ` +Type variable itself does not contain anything. -Here, we are using a new decorator called `@OneToOne`. It allows to -create one-to-one relations between two entities. `@OneToOne` decorator -accepts two arguments: - -* `type => Photo` is a function that returns the class of the entity with -which relation we want to make our relation. - -> we are forced to use a function that returns a class, instead of using -class directly, because of the language specifics. We can also write it -as a `() => Photo`, but we use `type => Photo` as convention to increase -code readability a bit. `type` variable itself does not contain anything. - -* `photo => photo.metadata` is a function that returns a name of the -*inverse side of the relation*. Here we show that `metadata` property -of the `Photo` class is where we store `PhotoMetadata` in the `Photo` class. - -> you could also instead of passing function that returns a property of the -photo simply pass a string to @OneToOne decorator, like "metadata". But -we used this function-typed approach to make your refactorings easier. - -We also put `@JoinColumn` decorator, that indicates that this side of the relationship -will be owning relationship. Using this decorator is required on owner side of the relationship. - -Now lets add inverse side of our relation to the `Photo` class: - -```typescript -export class Photo { - /// ... other columns - - @OneToOne(type => PhotoMetadata, metadata => metadata.photo) - metadata: PhotoMetadata; -} -``` - -In any relation there are always two sides and only one of them can be owner side. Owner side -is called "owner", because it "owns" relation id. In our example -`PhotoMetadata` owns relation because it has a `@JoinColumn` decorator, thus -it will contain photo id. `Photo` entity does not have `@JoinColumn`, thus -does not have metadata id, and it means that its inverse side of the relationship. +We also put `@JoinColumn` decorator, which indicates that this side of the relationship will be owning relationship. +Relations can be a uni-directional and bi-directional. +Only one side of relational can be owner. +Using this decorator is required on owner side of the relationship. -After you run application ORM will create a photo_metadata table: +If you run the app you'll see a new generated table, and it will contain a column with a foreign key for the photo relation: -``` +```shell +-------------+--------------+----------------------------+ -| photo_metadata | +| photo | +-------------+--------------+----------------------------+ | id | int(11) | PRIMARY KEY AUTO_INCREMENT | -| height | double | | -| width | double | | +| height | int(11) | | +| width | int(11) | | | comment | varchar(255) | | | compressed | boolean | | | orientation | varchar(255) | | @@ -329,63 +590,158 @@ After you run application ORM will create a photo_metadata table: +-------------+--------------+----------------------------+ ``` -Don't forget to register `PhotoMetadata` class for your connection in the ORM: +### persisting an object with one-to-one relation -```typescript -const options: CreateConnectionOptions = { - // ... other options - entities: [Photo, PhotoMetadata] -}; -``` - -Now lets insert metadata and photo to our database: +Now lets save a photo, its metadata and attach them to each other. ```typescript -createConnection(options).then(connection => { +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; +import {PhotoMetadata} from "./entity/PhotoMetadata"; - // create photo object +createConnection(/*...*/).then(async connection => { + + // create a photo let photo = new Photo(); photo.name = "Me and Bears"; photo.description = "I am near polar bears"; photo.filename = "photo-with-bears.jpg" photo.isPublished = true; - // create photo metadata object - let metadata = new PhotoMetadata(); - metadata.height = 640; - metadata.width = 480; - metadata.compressed = true; - metadata.comment = "cybershoot"; + // create a photo metadata + let metadata = new PhotoMetadata(); + metadata.height = 640; + metadata.width = 480; + metadata.compressed = true; + metadata.comment = "cybershoot"; metadata.orientation = "portait"; - metadata.photo = photo; // this way we connect them - + metadata.photo = photo; // this way we connect them + // get entity repositories let photoRepository = connection.getRepository(Photo); let metadataRepository = connection.getRepository(PhotoMetadata); - + // first we should persist a photo - photoRepository.persist(photo).then(photo => { - - // photo is saved. Now we need to persist a photo metadata - return metadataRepository.persist(metadata); - - }).then(metadata => { - // metadata is saved, and relation between metadata and photo is created in the database too + await photoRepository.persist(photo); + + // photo is saved. Now we need to persist a photo metadata + await metadataRepository.persist(metadata); + + // done + console.log("metadata is saved, and relation between metadata and photo is created in the database too"); +}); +``` + +### Adding inverse side of a relation + +Relations can be a uni-directional and bi-directional. +Now, relation between PhotoMetadata and Photo is uni-directional. +Owner of the relation is PhotoMetadata and Photo doesn't know anything about PhotoMetadata. +This makes complicated accessing a photo metadata from the photo objects. +To fix it we should add inverse relation and make relations between PhotoMetadata and Photo bi-directional. +Let's modify our entities: + +```typescript +import {Table, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn} from "typeorm"; +import {Photo} from "./Photo"; + +@Table() +export class PhotoMetadata { + + /* ... other columns */ + + @OneToOne(type => Photo, photo => photo.metadata) + @JoinColumn() + photo: Photo; +} +``` + +```typescript +import {Table, Column, PrimaryGeneratedColumn, OneToOne} from "typeorm"; +import {PhotoMetadata} from "./PhotoMetadata"; + +@Table() +export class Photo { + + /* ... other columns */ + + @OneToOne(type => PhotoMetadata, photoMetadata => photoMetadata.photo) + metadata: PhotoMetadata; +} +``` + +`photo => photo.metadata` is a function that returns a name of the inverse side of the relation. +Here we show that metadata property of the Photo class is where we store PhotoMetadata in the Photo class. +You could also instead of passing function that returns a property of the photo simply pass a string to `@OneToOne` decorator, like `"metadata"`. +But we used this function-typed approach to make your refactorings easier. + +Note that we should use `@JoinColumn` only on one side of relation. +On which side you put this decorator, that side will be owning side of relationship. +Owning side of relationship contain a column with a foreign key in the database. + +### Loading object with their relations + +Now lets load our photo, and its photo metadata in a single query. +There are two ways to do it - one you can use `FindOptions`, second is to use QueryBuilder. +Lets use FindOptions first. +`Repository.find` method allows you to specify object with FindOptions interface. +Using this you can customize your query to perform more complex queries. + +```typescript +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; +import {PhotoMetadata} from "./entity/PhotoMetadata"; + +createConnection(/*...*/).then(async connection => { + + /*...*/ + let photoRepository = connection.getRepository(Photo); + let photos = await photoRepository.find({ + alias: "photo", + innerJoinAndSelect: { + "metadata": "photo.metadata" + } }); }); ``` + +Here photos will contain array of photos from the database, and each photo will contain its photo metadata. + +`alias` is a required property of FindOptions. Its your own alias name of the data you are selecting. +You'll use this alias in your where, order by, group by, join and other expressions. -#### using cascade options to automatically save related objects +We also used `innerJoinAndSelect` to inner and join and select the data from photo.metadata. +In `"photo.metadata"` "photo" is an alias you used, and "metadata" is a property name with relation of the object you are selecting. +`"metadata"`: is a new alias to the data returned by join expression. -We can setup cascade options in our relations, in the cases when we want -our related object to be persisted whenever other object is saved. Let's -change our photo's `@OneToOne` decorator a bit: +Lets use `QueryBuilder` for the same purpose. QueryBuilder allows to use more complex queries in an elegant way: + +```typescript +import {createConnection} from "typeorm"; +import {Photo} from "./entity/Photo"; +import {PhotoMetadata} from "./entity/PhotoMetadata"; + +createConnection(/*...*/).then(async connection => { + + /*...*/ + let photoRepository = connection.getRepository(Photo); + let photos = await photoRepository.createQueryBuilder("photo") + .innerJoinAndSelect("photo.metadata") + .getResults(); + +}); +``` + +### using cascade options to automatically save related objects + +We can setup cascade options in our relations, in the cases when we want our related object to be persisted whenever other object is saved. +Let's change our photo's `@OneToOne` decorator a bit: ```typescript export class Photo { /// ... other columns - + @OneToOne(type => PhotoMetadata, metadata => metadata.photo, { cascadeInsert: true, cascadeUpdate: true, @@ -395,21 +751,16 @@ export class Photo { } ``` -* `cascadeInsert` automatically insert metadata in the relation if -it does not exist in its table. This means that we don't need to manually -insert a newly created photoMetadata object. -* `cascadeUpdate` automatically update metadata in the relation if -in this object something is changed -* `cascadeRemove` automatically remove metadata from its table if you -removed metadata from photo object +* **cascadeInsert** - automatically insert metadata in the relation if it does not exist in its table. + This means that we don't need to manually insert a newly created photoMetadata object. +* **cascadeUpdate** - automatically update metadata in the relation if in this object something is changed. +* **cascadeRemove** - automatically remove metadata from its table if you removed metadata from photo object. -Using `cascadeInsert` allows us not to separately persist photo and -separately persist metadata objects now. Now we can simply persist a -photo object, and metadata object will persist automatically because of -cascade options. +Using cascadeInsert allows us not to separately persist photo and separately persist metadata objects now. +Now we can simply persist a photo object, and metadata object will persist automatically because of cascade options. ```typescript -createConnection(options).then(connection => { +createConnection(options).then(async connection => { // create photo object let photo = new Photo(); @@ -419,68 +770,75 @@ createConnection(options).then(connection => { photo.isPublished = true; // create photo metadata object - let metadata = new PhotoMetadata(); - metadata.height = 640; - metadata.width = 480; - metadata.compressed = true; - metadata.comment = "cybershoot"; + let metadata = new PhotoMetadata(); + metadata.height = 640; + metadata.width = 480; + metadata.compressed = true; + metadata.comment = "cybershoot"; metadata.orientation = "portait"; - metadata.photo = photo; // this way we connect them - + metadata.photo = photo; // this way we connect them + // get repository let photoRepository = connection.getRepository(Photo); - + // first we should persist a photo - photoRepository.persist(photo).then(photo => { - console.log("Photo is saved, photo metadata is saved too.") - }); + await photoRepository.persist(photo); + console.log("Photo is saved, photo metadata is saved too.") }); -``` +``` -#### creating a many-to-one / one-to-many relation +### creating a many-to-one / one-to-many relation -Lets create a many-to-one / one-to-many relation. Lets say a photo has -one author, and each author can have many photos. First, lets create a -`Author` class: +Lets create a many-to-one / one-to-many relation. +Lets say a photo has one author, and each author can have many photos. +First, lets create Author class: ```typescript -import {Table} from "typeorm"; -import {PrimaryColumn, Column} from "typeorm"; -import {OneToMany} from "typeorm"; +import {Table, Column, PrimaryGeneratedColumn, OneToMany, JoinColumn} from "typeorm"; +import {Photo} from "./Photo"; -@Table("author") +@Table() export class Author { - - @PrimaryColumn("int", { generated: true }) + + @PrimaryGeneratedColumn() id: number; - + @Column() name: string; - + @OneToMany(type => Photo, photo => photo.author) // note: we will create author property in the Photo class below photos: Photo[]; } ``` -Now lets add inverse side of our relation to the `Photo` class: +Author contains an inverse side of a relationship. +OneToMany is always an inverse side of relation, and it can't exist without ManyToOne of the other side of relationship. + +Now lets add owner side of relationship into the Photo entity: ```typescript +import {Table, Column, PrimaryGeneratedColumn, ManyToOne} from "typeorm"; +import {PhotoMetadata} from "./PhotoMetadata"; +import {Author} from "./Author"; + +@Table() export class Photo { - /// ... other columns - + + /* ... other columns */ + @ManyToOne(type => Author, author => author.photos) author: Author; } ``` -In case of many-to-one / one-to-many relation, owner relation is **many-to-one**. -It means that class which uses `@ManyToOne` will store id of the related -object. +In many-to-one / one-to-many relation, owner side is always many-to-one. +It means that class which uses `@ManyToOne` will store id of the related object. -After you run application ORM will create **author** table: +After you run application ORM will create author table: -``` + +```shell +-------------+--------------+----------------------------+ | author | +-------------+--------------+----------------------------+ @@ -489,10 +847,9 @@ After you run application ORM will create **author** table: +-------------+--------------+----------------------------+ ``` -It will also modify **photo** table - add a new column `author` and create - a foreign key for it: - -``` +It will also modify photo table - add a new column author and create a foreign key for it: + +```shell +-------------+--------------+----------------------------+ | photo | +-------------+--------------+----------------------------+ @@ -504,69 +861,25 @@ It will also modify **photo** table - add a new column `author` and create | author | int(11) | FOREIGN KEY | +-------------+--------------+----------------------------+ ``` + +### creating a many-to-many relation -Don't forget to register `Author` class for your connection in the ORM: - -```typescript -const options: CreateConnectionOptions = { - // ... other options - entities: [Photo, PhotoMetadata, Author] -}; -``` - -Now lets insert author and photo to our database: - -```typescript -createConnection(options).then(connection => { - - // create a new user - let author = new Author(); - author.name = "Umed Khudoiberdiev"; - - // create photo object - let photo = new Photo(); - photo.name = "Me and Bears"; - photo.description = "I am near polar bears"; - photo.filename = "photo-with-bears.jpg" - photo.author = author; - - // get entity repositories - let photoRepository = connection.getRepository(Photo); - let authorRepository = connection.getRepository(Author); - - // first we should persist our user - authorRepository.persist(author).then(author => { - - // author is saved. Now we need to persist a photo - return photoRepository.persist(photo); - - }).then(photo => { - // photo is saved, and relation between photo and author is created in the database too - }); - -}); -``` - -#### creating a many-to-many relation - -Lets create a many-to-one / many-to-many relation. Lets say a photo can -be in many albums, and multiple can have many photos. Lets create an -`Album` class: +Lets create a many-to-one / many-to-many relation. +Lets say a photo can be in many albums, and multiple can have many photos. +Lets create an `Album` class: ```typescript -import {Table} from "typeorm"; -import {PrimaryColumn, Column} from "typeorm"; -import {ManyToMany, JoinTable} from "typeorm"; +import {Table, PrimaryGeneratedColumn, Column, ManyToMany, JoinTable} from "typeorm"; -@Table("album") +@Table() export class Album { - - @PrimaryColumn("int", { generated: true }) + + @PrimaryGeneratedColumn() id: number; - + @Column() name: string; - + @ManyToMany(type => Photo, album => photo.albums, { // note: we will create "albums" property in the Photo class below cascadeInsert: true, // allow to insert a new photo on album save cascadeUpdate: true, // allow to update a photo on album save @@ -576,7 +889,7 @@ export class Album { photos: Photo[] = []; // we initialize array for convinience here } ``` - + `@JoinTable` is required to specify that this is owner side of the relationship. Now lets add inverse side of our relation to the `Photo` class: @@ -584,29 +897,27 @@ Now lets add inverse side of our relation to the `Photo` class: ```typescript export class Photo { /// ... other columns - + @ManyToMany(type => Album, album => album.photos, { - cascadeInsert: true, // allow to insert a new album on photo save - cascadeUpdate: true, // allow to update an album on photo save - cascadeRemove: true // allow to remove an album on photo remove + cascadeInsert: true, // allow to insert a new album on photo save + cascadeUpdate: true, // allow to update an album on photo save + cascadeRemove: true // allow to remove an album on photo remove }) albums: Album[] = []; // we initialize array for convinience here } ``` -After you run application ORM will create a **album_photos_photo_albums** -*junction table*: +After you run application ORM will create a **album_photos_photo_albums** *junction table*: -``` +```shell +-------------+--------------+----------------------------+ | album_photos_photo_albums | +-------------+--------------+----------------------------+ -| album_id_1 | int(11) | FOREIGN KEY | -| photo_id_2 | int(11) | FOREIGN KEY | +| album_id_1 | int(11) | PRIMARY KEY FOREIGN KEY | +| photo_id_2 | int(11) | PRIMARY KEY FOREIGN KEY | +-------------+--------------+----------------------------+ ``` - Don't forget to register `Album` class for your connection in the ORM: ```typescript @@ -615,186 +926,94 @@ const options: CreateConnectionOptions = { entities: [Photo, PhotoMetadata, Author, Album] }; ``` - + Now lets insert author and photo to our database: ```typescript -createConnection(options).then(connection => { +let connection = await createConnection(options); - // create a few albums - let album1 = new Album(); - album1.name = "Bears"; - - let album2 = new Album(); - album2.name = "Me"; - - // create a few photos - let photo1 = new Photo(); - photo1.name = "Me and Bears"; - photo1.description = "I am near polar bears"; - photo1.filename = "photo-with-bears.jpg" - - let photo2 = new Photo(); - photo2.name = "Me and Bears"; - photo2.description = "I am near polar bears"; - photo2.filename = "photo-with-bears.jpg" - - // get entity repository - let photoRepository = connection.getRepository(Photo); - - // we only save a photos, albums are persisted automatically because of cascade options - photoRepository - .persist(photo1) // first save a first photo - .then(photo => photoRepository.persist(photo2)) // second save a second photo - .then(photo => console.log("Both photos have been saved")); - -}); -``` +// create a few albums +let album1 = new Album(); +album1.name = "Bears"; -#### using FindOptions to customize find queries +let album2 = new Album(); +album2.name = "Me"; -`Repository.find` method allows you to specify `findOptions`. Using this -you can customize your query to perform more complex queries. For example -you can do this: +// create a few photos +let photo1 = new Photo(); +photo1.name = "Me and Bears"; +photo1.description = "I am near polar bears"; +photo1.filename = "photo-with-bears.jpg" -```typescript +let photo2 = new Photo(); +photo2.name = "Me and Bears"; +photo2.description = "I am near polar bears"; +photo2.filename = "photo-with-bears.jpg" + +// get entity repository let photoRepository = connection.getRepository(Photo); -photoRepository.find({ - alias: "photo", // this is alias of what you are selecting - photos. You must specify it. - innerJoinAndSelect: [ - "photo.metadata" - ], - leftJoinAndSelect: [ - "photo.albums" - ], - where: "photo.isPublished=true AND (photo.name=:photoName OR photo.name=:bearName)", - orderBy: [{ sort: "photo.id", order: "DESC" }], - firstResult: 5, - maxResults: 10, - parameters: { - photoName: "My", - bearName: "Mishka" - } -}).then(photos => { - console.log(photos); -}); -``` -`photoRepository.find` will select you all photos that are published and -whose name is "My" or "Mishka", it will select results from 5 position -(pagination offset), and will select only 10 results (pagination limit). -Selection result will be ordered by id in descending order. Photo's albums -will be left-joined and photo's metadata will be inner joined. +// first save a first photo +// we only save a photos, albums are persisted +// automatically because of cascade options +await photoRepository.persist(photo1); -Learn more about FindOptions [here](docs/repository.md#find-options). +// second save a first photo +await photoRepository.persist(photo2); -#### using QueryBuilder to build complex queries +console.log("Both photos have been saved"); +``` -You can use `QueryBuilder` to build even more complex queries. For example -you can do this: +### using QueryBuilder +You can use QueryBuilder to build even more complex queries. For example you can do this: ```typescript let photoRepository = connection.getRepository(Photo); -photoRepository - .createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it. +let photos = await photoRepository + .createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it. .innerJoinAndSelect("photo.metadata") .leftJoinAndSelect("photo.albums") .where("photo.isPublished=true") - .andWhere("photo.name=:photoName OR photo.name=:bearName") + .andWhere("(photo.name=:photoName OR photo.name=:bearName)") .orderBy("photo.id", "DESC") .setFirstResult(5) .setMaxResults(10) - .setParameters({ photoName: "My", beaName: "Mishka" }) - .getResults().then(photos => console.log(photos)); + .setParameters({ photoName: "My", bearName: "Mishka" }) + .getResults(); ``` -This query builder will select you all photos that are published and -whose name is "My" or "Mishka", it will select results from 5 position -(pagination offset), and will select only 10 results (pagination limit). -Selection result will be ordered by id in descending order. Photo's albums -will be left-joined and photo's metadata will be inner joined. - -Learn more about QueryBuilder [here](docs/query-builder.md). - -#### using EntityManager to work with any entity - -Sometimes you may want to simplify what you are doing and not to create -a `repository` instance for each of your entity to, for example, persist -it. In such cases you may want to use EntityManager. These are several -methods from EntityManager class: - -```typescript -// create a new user -let author = new Author(); -author.name = "Umed Khudoiberdiev"; - -// create photo metadata -let metadata = new PhotoMetadata(); -metadata.height = 640; -metadata.width = 480; -metadata.compressed = true; -metadata.comment = "cybershoot"; -metadata.orientation = "portait"; -metadata.photo = photo; // this way we connect them - -// create a new photo -let photo = new Photo(); -photo.name = "Me and Bears"; -photo.description = "I am near polar bears"; -photo.filename = "photo-with-bears.jpg"; -photo.author = author; -photo.metadata = metadata; - -let entityManager = connection.getEntityManager(); - -// first lets persist entities -entityManager - .persist(author) // first lets save a new author - .then(savedAuthor => entityManager.persist(metadata)); // then save a new metadata - .then(savedMetadata => entityManager.persist(photo)); // and finally save a photo - .then(savedPhoto => { - console.log("Everything is saved without using repositories") - - // next example is about finding entity and removing it - entityManager.find(Photo, { isPublished: true }).then(photos => { - - // and final example about removing entities - return Promise.all(photos.map(photo => entityManager.remove(photo))); - }); - - }); -``` +This query builder will select you all photos that are published and whose name is "My" or "Mishka", +it will select results from 5 position (pagination offset), +and will select only 10 results (pagination limit). +Selection result will be ordered by id in descending order. +Photo's albums will be left-joined and photo's metadata will be inner joined. -Learn more about EntityManager [here](docs/entity-manager.md). +You'll use query builder in your application a lot. Learn more about QueryBuilder [here](https://typeorm.github.io/query-builder.html). ## Learn more -* [connection and connection options](docs/connection-and-connection-options.md) -* [databases and drivers](docs/databases-and-drivers.md) -* [updating database schema](docs/updating-database-schema.md) -* [tables and table inheritance](docs/tables-and-table-inheritance.md) -* [table columns](docs/table-columns.md) -* [relations](docs/relations.md) -* [indices and keys](docs/indices-and-keys.md) -* [repository](docs/repository.md) -* [query builder](docs/query-builder.md) -* [entity manager](docs/entity-manager.md) -* [subscribers and entity listeners](docs/subscribers-and-entity-listeners.md) -* [naming strategies](docs/naming-strategies.md) -* [decorators reference](docs/decorators-reference.md) -* [command line tools](docs/command-line-tools.md) +* [Connection and connection options](https://typeorm.github.io/connection.html) +* [Connection Manager](https://typeorm.github.io/connection-manager.html) +* [Databases and drivers](https://typeorm.github.io/databases-and-drivers.html) +* [Updating database schema](https://typeorm.github.io/updating-database-schema.html) +* [Tables and columns](https://typeorm.github.io/tables-and-columns.html) +* [Relations](https://typeorm.github.io/relations.html) +* [Indices](https://typeorm.github.io/indices.html) +* [Repository](https://typeorm.github.io/repository.html) +* [Query Builder](https://typeorm.github.io/query-builder.html) +* [Entity Manager](https://typeorm.github.io/entity-manager.html) +* [Subscribers and entity listeners](https://typeorm.github.io/subscribers-and-entity-listeners.html) +* [Using service container](https://typeorm.github.io/using-service-container.html) +* [Decorators Reference](https://typeorm.github.io/decorators-reference.html) ## Samples -Take a look on samples in [./sample](sample) for more examples of -usages. - -## Todos +Take a look on samples in [./sample](sample) for examples of usage. -ORM development is in progress. Api can be changed a lot. More documentation and features expected to be soon. -Feel free to contribute. +## Contributing +Learn about contribution [here](CONTRIBUTING.md) and how to setup your development environment [here](DEVELOPER.md). [1]: https://en.wikipedia.org/wiki/Object-relational_mapping diff --git a/docs/command-line-tools.md b/docs/command-line-tools.md deleted file mode 100644 index 0bc40df914..0000000000 --- a/docs/command-line-tools.md +++ /dev/null @@ -1,12 +0,0 @@ -## Command Line Tools - -TODO: first need to create gulp package and tasks to perform following command line operations: - -* create a schema -* update a schema -* drop a schema -* create entity -* create subscriber -* create custom repository? -* create a backup? -* ..... \ No newline at end of file diff --git a/docs/connection-and-connection-options.md b/docs/connection-and-connection-options.md deleted file mode 100644 index 7ec0956c87..0000000000 --- a/docs/connection-and-connection-options.md +++ /dev/null @@ -1,159 +0,0 @@ -## Connection and connection options - -You start using ORM by creating a connection with the database. In this section you will learn about: - -* [Connection Manager that contains all connections to the databases](#connection-manager) -* [Working with connections](#working-with-connection) -* [Connection options](#connection-options) -* [How to use connection manager, connections and connection options](#example) - -### Connection Manager - -Connection manager allows to create a new connections and retrieve previously created connections. Also it allows to import -entities and subscribers into specific connection. These are main public methods of the `ConnectionManager`: - -* `createConnection(options: CreateConnectionOptions): Connection` - -Creates a new connection and registers it in the connection manager. It returns a newly created connection. -New connection will have a given *connection name*. If connection name is not given then "default" will be used as a - connection name. This name will be used to retrieve this connection later. -*Driver* needs to be specified to understand what kind of database will be used for this connection. -Right now it can be only a `mysql` driver. -*Options* specifies connection options. More about it later. - -* `getConnection(connectionName: string = "default"): Connection` - -Gets a connection with a given name that was created using `createConnection` method. Connection later can be used to -perform actions on it. -* `importEntities(connectionName: string = "default", entities: Function[]): void` - -Imports all given entities and registers them in the connection with a given name. -* `importSubscribers(connectionName: string = "default", subscribers: Function[]): void` - -Imports all given subscribers and registers them in the connection with a given name. -* `importEntitiesFromDirectories(connectionName: string = "default", paths: string[]): void` - -Imports all entities from the given directories and registers them in the connection with a given name. -Paths is an array of directories from where to import entities. -* `importSubscribersFromDirectories(connectionName: string = "default", paths: string[]): void` - -Imports all subscribers from the given directories and registers them in the connection with a given name. -Paths is an array of directories from where to import subscribers. - -### Working with Connection - -Connection is a database connection to specific database of the specific database management system. -There are several useful methods in the Connection object: - -* `connect()` -Opens a new connection with the database. -* `close()` -Closes connection with the database. -* `getEntityManager()` -Gets [EntityManager](entity-manager.md) that is used to execute database operations on any entity -that is registered in this connection. -* `getRepository(entityClass: Function)` -Gets a [repository](repository.md) of the specific entity that provides all functionality -(selects/inserts/updates/deletes) with a table of the given entity class. - -### Connection Options - -To perform a connection you need to specify a connection options. ConnectionOptions is an interface: - -```typescript -export interface ConnectionOptions { - - url?: string; // connection url - host?: string; // database host - port?: number; // database host port - username?: string; // database username - password?: string; // database password - database?: string; // database name - autoSchemaSync?: boolean; // set to true if you want your database schema to be auto created on each application launch - logging?: { - - logger?: (message: any, level: string) => void; // some specific logger to be used. By default it is a console - logQueries?: boolean; // used if you want to log every executed query - logOnlyFailedQueries?: boolean; // used if you want to log only failed query - logFailedQueryError?: boolean; // used if you want to log error of the failed query - - }; - -} -``` - -* To perform a connection you either must specify a connection `url`, either specify `host/port/username/password/database`. -* `autoSchemaSync` allows you to automatically synchronize your database schema (create new tables, -remove/rename old columns, create foreign keys, etc.) on each application run. Note that there can be errors in schema -synchronization (mostly errors can be caused by unresolved foreign keys) and this will crash your application. -This option should not be used in production, only during development and only if you are too lazy to use -[command line tools](command-line-tools.md). Alternatively you can use [schema update gulp plugin](todo). - - -### Example - -```typescript -// create a new connection manager -let connectionManager = new ConnectionManager(); - -// prepare connection options -let connectionOptions: ConnectionOptions = { - host: "localhost", - port: 3306, - username: "root", - password: "admin", - database: "test", - autoSchemaSync: true -}; - -// create a new connection with mysql driver -connectionManager.createConnection(new MysqlDriver(connectionOptions)); - -// import all entities from specific directory -connectionManager.importEntitiesFromDirectories(__dirname + "/entities"); - -// get our connection: -let connection = connectionManager.getConnection(); -connection.connect().then(connection => { - - // now we are connected to the database - // here we have a connection and we can use any of its methods - // lets say we have a Photo entity in the /entities directory - - // and lets create a new Photo entity instance - - // lets try to use entity manager - let entityManager = connection.getEntityManager(); - - // and lets create a new Photo entity instance - let photo = new Photo(); - photo.name = "photo #1"; - - // and save it using entity manager - entityManager - .persist(photo) - .then(photo => { - console.log("Photo has been saved using entity manager"); - }); - - // lets try to use repository - let repository = connection.getRepository(Photo); - - // and lets create a new Photo entity instance - let photo = new Photo(); - photo.name = "photo #2"; - - // and save it using repository - repository - .persist(photo) - .then(photo => { - console.log("Photo has been saved using repository"); - }); - - -}).catch(error => { - // looks like some error during connection. Lets log it to find details - console.log("error during connection to the database ", error); -}); -``` - diff --git a/docs/databases-and-drivers.md b/docs/databases-and-drivers.md deleted file mode 100644 index e492877ccf..0000000000 --- a/docs/databases-and-drivers.md +++ /dev/null @@ -1,36 +0,0 @@ -## Databases and drivers - -ORM is working with databases. To communicate with database it uses **database drivers**. -Database Driver communicates with specific database and performs queries you need. - -### Driver Interface - -Right now only `mysql` database driver is supported. If you need other driver, or you simply -want to contribute then feel free to add it - adding new drivers is not complicated. - -Drivers are typically used within connection: - -`let connection = connectionManager.createConnection("my-connection", new MysqlDriver());` - -To access the driver you can do it from your connection instance: - -`connection.driver` - -There are several useful methods in the driver object: - -* `driver.native` - -Allows you to access a native layer of the driver. For example MysqlDriver is depend of npm's `mysql` package -and `mysqlDriver.native` will give you instance to that package. - -* `driver.nativeConnection` - -Allows you to access to a connection instance of the native layer of the driver. For example MysqlDriver is depend of -npm's `mysql` package and when we create a connection using that package it returns us some `connection instance` that - we save in the driver.nativeConnection. - -Both methods are not recommended to use, but sometimes you have no choice because of the limits of the ORM abstraction -and you have to use them. In such situations both methods are very useful. - -There are lot of other methods in the Driver, but you won't use them directly - they are used by other components, like -[Entity Manager](entity-manager.md) or [Repository](repository.md). You should use those components instead. diff --git a/docs/decorators-reference.md b/docs/decorators-reference.md deleted file mode 100644 index 505ae7b8c3..0000000000 --- a/docs/decorators-reference.md +++ /dev/null @@ -1,196 +0,0 @@ -## Decorators Reference - -* Table Decorators - * [@Table](#table) - * [@AbstractTable](#abstract-table) -* Column Decorators - * [@Column](#column) - * [@PrimaryColumn](#primary-column) - * [@CreateDateColumn](#create-date-column) - * [@UpdateDateColumn](#update-date-column) -* Relation Decorators - * [@OneToOne](#one-to-one) - * [@OneToOneInverse](#one-to-one-inverse) - * [@ManyToOne](#many-to-one) - * [@OneToMany](#one-to-many) - * [@ManyToMany](#many-to-many) - * [@ManyToManyInverse](#many-to-many-inverse) -* Subscriber and Listener Decorators - * [@EventSubscriber](#event-subscriber) - * [@AfterLoad](#after-load) - * [@BeforeInsert](#before-insert) - * [@AfterInsert](#after-insert) - * [@BeforeUpdate](#before-update) - * [@AfterUpdate](#after-update) - * [@BeforeRemove](#before-remove) - * [@AfterRemove](#after-remove) -* Indices - * [@Index](#index) - * [@CompoundIndex](#compound-index) - -### Table Decorators - -#### @Table - -`@Table(name: string)` - -This decorator is used to mark classes that will be a tables. Database schema will be created for all classes -decorated with it, and Repository can be retrieved and used for it. - -#### @AbstractTable - -`@AbstractTable()` - -Allows to use columns and relations data from the inherited metadata. - -### Column Decorators - -#### @Column - -`@Column(options?: ColumnOptions)` -`@Column(type?: ColumnType, 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. - -#### @PrimaryColumn - -`@PrimaryColumn(options?: ColumnOptions)` -`@PrimaryColumn(type?: ColumnType, 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. Primary columns also creates a PRIMARY KEY for -this column in a db. - -#### @CreateDateColumn - -`@CreateDateColumn(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. - -#### @UpdateDateColumn - -`@UpdateDateColumn(options?: ColumnOptions)` - -This column will store an update date of the updated object. This date is being updated each time you persist the -object. - -### Relation Decorators - -#### @OneToOne - -`@OneToOne(typeFunction: (type?: any) => Function, options?: RelationOptions)` -`@OneToOne(typeFunction: (type?: any) => Function, inverseSide?: string|((object: T) => any), options?: RelationOptions)` - -One-to-one relation allows to create direct relation between two entities. Entity1 have only one Entity2. -Entity1 is an owner of the relationship, and storages Entity1 id on its own side. - -#### @OneToOneInverse - -`@OneToOneInverse(typeFunction: (type?: any) => Function, options?: RelationOptions)` -`@OneToOneInverse(typeFunction: (type?: any) => Function, inverseSide?: string|((object: T) => any), options?: RelationOptions)` - -Inverse side of the one-to-one relation. One-to-one relation allows to create direct relation between two entities. -Entity2 have only one Entity1. Entity2 is inverse side of the relation on Entity1. Does not storage id of the -Entity1. Entity1's id is storage on the one-to-one owner side. - -#### @ManyToOne - -`@ManyToOne(typeFunction: (type?: any) => Function, options?: RelationOptions)` -`@ManyToOne(typeFunction: (type?: any) => Function, inverseSide?: string|((object: T) => any), options?: RelationOptions)` - -Many-to-one relation allows to create type of relation when Entity1 can have single instance of Entity2, but -Entity2 can have a multiple instances of Entity1. Entity1 is an owner of the relationship, and storages Entity2 id -on its own side. - -#### @OneToMany - -`@OneToMany(typeFunction: (type?: any) => Function, options?: RelationOptions)` -`@OneToMany(typeFunction: (type?: any) => Function, inverseSide?: string|((object: T) => any), options?: RelationOptions)` - -One-to-many relation allows to create type of relation when Entity2 can have multiple instances of Entity1. -Entity1 have only one Entity2. Entity1 is an owner of the relationship, and storages Entity2 id on its own side. - -#### @ManyToMany - -`@ManyToMany(typeFunction: (type?: any) => Function, options?: RelationOptions)` -`@ManyToMany(typeFunction: (type?: any) => Function, inverseSide?: string|((object: T) => any), options?: RelationOptions)` - -Many-to-many is a type of relationship when Entity1 can have multiple instances of Entity2, and Entity2 can have -multiple instances of Entity1. To achieve it, this type of relation creates a junction table, where it storage -entity1 and entity2 ids. This is owner side of the relationship. - -#### @ManyToManyInverse - -`@ManyToManyInverse(typeFunction: (type?: any) => Function, options?: RelationOptions)` -`@ManyToManyInverse(typeFunction: (type?: any) => Function, inverseSide?: string|((object: T) => any), options?: RelationOptions)` - -Many-to-many is a type of relationship when Entity1 can have multiple instances of Entity2, and Entity2 can have -multiple instances of Entity1. To achieve it, this type of relation creates a junction table, where it storage -entity1 and entity2 ids. This is inverse side of the relationship. - -### Subscriber and Listener Decorators - -#### @EventSubscriber - -`@EventSubscriber()` - -Classes decorated with this decorator will listen to ORM events and their methods will be triggered when event -occurs. Those classes must implement EventSubscriberInterface. - -#### @AfterLoad - -`@AfterLoad()` - - * Calls a method on which this decorator is applied after entity is loaded. - -#### @BeforeInsert - -`@BeforeInsert()` - -Calls a method on which this decorator is applied before this entity insertion. - -#### @AfterInsert - -`@AfterInsert()` - -Calls a method on which this decorator is applied after this entity insertion. - -#### @BeforeUpdate - -`@BeforeUpdate()` - -Calls a method on which this decorator is applied before this entity update. - -#### @AfterUpdate - -`@AfterUpdate()` - -Calls a method on which this decorator is applied after this entity update. - -#### @BeforeRemove - -`@BeforeRemove()` - -Calls a method on which this decorator is applied before this entity removal. - -#### @AfterRemove - -`@AfterRemove()` - -Calls a method on which this decorator is applied after this entity removal. - -### Indices - -#### @Index - -`@Index(name?: string)` - -Fields that needs to be indexed must be marked with this decorator. - -#### @CompoundIndex - -`@CompoundIndex(fields: string[])` - -Compound indexes must be set on entity classes and must specify fields to be indexed. \ No newline at end of file diff --git a/docs/entity-manager.md b/docs/entity-manager.md deleted file mode 100644 index c8675bc416..0000000000 --- a/docs/entity-manager.md +++ /dev/null @@ -1,70 +0,0 @@ -## Entity Manager - -EntityManager provides functionality to work with all your entities in a single connection. -Its like a Repository, but works with all entities. -There are several useful methods of the EntityManager: - -* `getRepository(entity: Entity): Repository` - -Gets the repository of the given entity. - -* `hasId(entity: Entity): boolean` - -Sometimes you want to check if your entity already has id or not. This maybe useful in situations when you want to -check if your object is new or not. - -* `create(entityClass: Function, plainJsObject?: Object): Entity` - -Creates a new empty instance of entity. If `plainJsObject` is given then new entity will be created and all properties -from the `plainJsObject` that can be mapped to this entity will be copied to the new entity. - -* `createMany(entityClass: Function, plainJsObjects: Object[]): Entity[]` - -Creates multiple new entities based on array of plain javascript objects. Properties for each plain javascript object -will be copied to newly created entities if they should exist there. - -* `initialize(entityClass: Function, object: Object): Promise` - -Creates a new entity from the given plan javascript object. If entity already exist in the database, then -it loads it (and everything related to it), replaces all values with the new ones from the given object -and returns this new entity. This new entity is actually a loaded from the db entity with all properties -replaced from the new object. - -* `persist(entity: Entity): Promise` - -Persists (saves) a given entity in the database. If entity does not exist in the database then it inserts it, -else if entity already exist in the database then it updates it. - -* `remove(entity: Entity): Promise` - -Removes a given entity from the database. - -* `find(entityClass: Function, conditions?: Object, options?: FindOptions): Promise` - -Finds entities that match given conditions or given find options. - -* `findOne(entityClass: Function, conditions?: Object, options?: FindOptions): Promise` - -Finds the first entity that match given conditions or given find options. - -* `findOneById(entityClass: Function, id: any, options?: FindOptions): Promise` - -Finds entity with a given entity id. - -* `findAndCount(entityClass: Function, conditions?: Object, options?: FindOptions): Promise<[Entity[], number]>` - -Finds entities that match given conditions or given find options plus gets a overall count of this items (for -pagination purposes). - -* `createQueryBuilder(entityClass: Function, alias: string): QueryBuilder` - -Creates a new query builder that can be used to build a sql query and get the results of the executed query. You can -learn more about query builder [here](docs/query-builder.md). - -* `query(sql: string): Promise` - -Executes raw SQL query. - -* `transaction(runInTransaction: () => Promise): Promise` - -Executes everything in the given function in a single transaction. \ No newline at end of file diff --git a/docs/indices-and-keys.md b/docs/indices-and-keys.md deleted file mode 100644 index cc00a32f26..0000000000 --- a/docs/indices-and-keys.md +++ /dev/null @@ -1,3 +0,0 @@ -## Indices and keys - -TBD \ No newline at end of file diff --git a/docs/naming-strategies.md b/docs/naming-strategies.md deleted file mode 100644 index 3c00a349d0..0000000000 --- a/docs/naming-strategies.md +++ /dev/null @@ -1,54 +0,0 @@ -## Naming strategies - -NamingStrategy is an interface that defines how auto-generated names for such things like table name, or table column -will be named. - -#### Interface - -By default `DefaultNamingStrategy` is used. -You can implement your own strategies by creating a new class and implementing `NamingStrategy` interface. -There are three methods you need to implement: - -* `tableName(className: string): string` - -Gets table name from the class name. `DefaultNamingStrategy` transforms className to a sneak-case. - -* `columnName(className: string): string` - -Gets column name from the class property. `DefaultNamingStrategy` transforms className to a camelCase. - -* `relationName(className: string): string` - -Gets relation name from the class property. `DefaultNamingStrategy` transforms className to a camelCase. - -#### Example - -Lets create a simple naming strategy that will add a "_" before names of tables, columns and relations: - -```typescript -export class MyNamingStrategy implements NamingStrategy { - - - tableName(className: string) { - return "_" + className; - } - - columnName(propertyName: string) { - return "_" + propertyName; - } - - relationName(propertyName: string) { - return "_" + propertyName; - } - -} -``` - -We also need to specify our new naming strategy on connection manager creation: - -```typescript -let connectionManager = new ConnectionManager(); -connectionManager.namingStrategy = new MyNamingStrategy(); -``` - -Now try to run and generate your database schema. New naming strategy should be used. \ No newline at end of file diff --git a/docs/query-builder.md b/docs/query-builder.md deleted file mode 100644 index 2d0a8b438f..0000000000 --- a/docs/query-builder.md +++ /dev/null @@ -1,148 +0,0 @@ -## Query Builder - -Query Builder allows to build a SQL queries and loads your entities from the database based on the built -query. - -#### create a query builder - -To create a query builder use [EntityManager](entity-manager.md) or [Repository](repository.md): - -```typescript -let repository = connection.getRepository(Photo); -let queryBuilder = repository.createQueryBuilder("photo"); -``` - -First argument of the `createQueryBuilder` is an **alias** of the object you are selecting. Basically its the same -alias as **sql aliases**. You'll need it to make further selections. - -#### build a query - -There are bunch of methods to help to create a query using QueryBuilder: - -* `queryBuilder.select(selection: string)` -* `queryBuilder.select(selection: string[])` -* `queryBuilder.select(...selection: string[])` -* `queryBuilder.addSelect(selection: string)` -* `queryBuilder.addSelect(selection: string[])` -* `queryBuilder.addSelect(...selection: string[])` - -Sets / adds given selections to the sql's `SELECT` group. Here you usually select aliases. - -* `queryBuilder.where(condition: string, parameters?: { [key: string]: any })` -* `queryBuilder.andWhere(condition: string, parameters?: { [key: string]: any })` -* `queryBuilder.orWhere(condition: string, parameters?: { [key: string]: any })` - -Adds sql's `WHERE` condition. For `andWhere` method it will also add "AND" before the condition and for the -`orWhere` method it will also add "OR" before the condition. `parameters` is the array of parameters to be escaped, -just a convenient shortcut for the `QueryBuilder#addParameters` method. - -* `queryBuilder.having(condition: string, parameters?: { [key: string]: any })` -* `queryBuilder.andHaving(condition: string, parameters?: { [key: string]: any })` -* `queryBuilder.orHaving(condition: string, parameters?: { [key: string]: any })` - -Adds sql's `HAVING` condition. For `andHaving` method it will also add "AND" before the condition and for the -`orHaving` method it will also add "OR" before the condition. `parameters` is the array of parameters to be escaped, -just a convenient shortcut for the `QueryBuilder#addParameters` method. - -* `queryBuilder.orderBy(sort: string, order: "ASC"|"DESC" = "ASC")` -* `queryBuilder.addOrderBy(sort: string, order: "ASC"|"DESC" = "ASC")` - -Sets / adds sql's `ORDER BY` condition. - -* `queryBuilder.groupBy(groupBy: string)` -* `queryBuilder.addGroupBy(groupBy: string)` - -Sets / adds sql's `GROUP BY` condition. - -* `queryBuilder.setLimit(limit: number)` - -Set's sql's `LIMIT`. If you are implementing pagination, LIMIT is not what you want in most of cases, because -of how it works. It can work in some trivial queries, but in complex queries it fails. If you want to use pagination -then use `QueryBuilder#setMaxResults` instead. - -* `queryBuilder.setOffset(limit: number)` - -Set's sql's `OFFSET`. If you are implementing pagination, OFFSET is not what you want in most of cases, because -of how it works. It can work in some trivial queries, but in complex queries it fails. If you want to use pagination -then use `QueryBuilder#setFirstResult` instead. - -* `queryBuilder.setFirstResult(firstResult: number)` - -Real "LIMIT" to use in queries for the pagination purposes. Use it if you want pagination. - -* `queryBuilder.setMaxResults(maxResults: number)` - -Real "LIMIT" to use in queries for the pagination purposes. Use it if you want pagination. - -* `setParameter(key: string, value: any)` -* `setParameters(parameters: Object)` -* `addParameters(parameters: Object)` - -Sets a single parameter value / set an object of parameters / add all parameters from the object. -Parameters are escaped values to be used in your query, like in WHERE or HAVING expressions. - -* `innerJoin(property: string, alias: string, conditionType?: "on"|"with", condition?: string)` - -Adds sql's `INNER JOIN` selection on a given **property** of the object, and gives an **alias** to this selection. -You can also specify a SQL JOIN condition type and condition. - -* `leftJoin(property: string, alias: string, conditionType?: "on"|"with", condition?: string)` - -Adds sql's `LEFT JOIN` selection on a given **property** of the object, and gives an **alias** to this selection. -You can also specify a SQL JOIN condition type and condition. - -* `innerJoinAndSelect(property: string, alias: string, conditionType?: "on"|"with", condition?: string)` - -Adds sql's `INNER JOIN` selection on a given **property** of the object, and gives an **alias** to this selection. -You can also specify a SQL JOIN condition type and condition. Also adds an alias into `SELECT`. - -* `leftJoinAndSelect(property: string, alias: string, conditionType?: "on"|"with", condition?: string)` - -Adds sql's `LEFT JOIN` selection on a given **property** of the object, and gives an **alias** to this selection. -You can also specify a SQL JOIN condition type and condition. Also adds an alias into `SELECT`. - -These were methods to help you to create a query. Here is example how to use some of them: - -```typescript -let photoRepository = connection.getRepository(Photo); -photoRepository - .createQueryBuilder("photo") // first argument is an alias. Alias is what you are selecting - photos. You must specify it. - .innerJoinAndSelect("photo.metadata") - .leftJoinAndSelect("photo.albums") - .where("photo.isPublished=true") - .andWhere("photo.name=:photoName OR photo.name=:bearName") - .orderBy("photo.id", "DESC") - .setFirstResult(5) - .setMaxResults(10) - .setParameters({ photoName: "My", beaName: "Mishka" }); -``` - -There is also a `getSql()` method that can be used to take a look what query QueryBuilder built for you -and debug your queries. - -#### get results - -There are several methods to help you to get results: - -* `getResults()` - -Gets all results of the query and transforms results to the your entities. - -* `getSingleResult()` - -Gets the first result of the query and transforms it to the entity. - -* `getScalarResults()` - -Gets all results of the query in a raw - it does not transform them to entities. - -* `getSingleScalarResult()` - -Gets the first result of the query in a raw - it does not transform it to entity. - -* `getCount()` - -Gets the count - number of rows returned by this selection. Can be used for pagination. - -There is also a `execute()` method that simply executes your query and returns you a plain result returned -by the database driver. \ No newline at end of file diff --git a/docs/relations.md b/docs/relations.md deleted file mode 100644 index b804df793e..0000000000 --- a/docs/relations.md +++ /dev/null @@ -1,41 +0,0 @@ -## Relations - -One of the best sides of TypeORM is relations support. You can build relations between your tables -easily without thinking of database schema and foreign keys. Relations are created using special decorators -on specific fields of entity objects. - -* [@OneToOne and @OneToOneInverse decorators](#onetoone-and-onetooneinverse-decorators) -* [@OneToMany and @ManyToOne decorators](#onetomany-and-manytoone-decorators) -* [@ManyToMany and @ManyToManyInverse decorators](#manytomany-and-manytomanyinverse-decorators) -* [Self referencing](#self-referencing) -* [Relational decorators options](#relational-decorators-options) - -### @OneToOne and @OneToOneInverse decorators - -TBD - -### @OneToMany and @ManyToOne decorators - -TBD - -### @ManyToMany and @ManyToManyInverse decorators - -TBD - -### Self referencing - -TBD - -### Relational decorators options - -RelationOptions is an object with additional relation options: - -* `name?: string` - column name for the relation in the database -* `cascadeInsert?: boolean` - allow cascade insert operations or not. If you set this to true, then any entity that -is new (means does not exist in the database) on the relation will be persisted too. -* `cascadeUpdate?: boolean` - allow cascade update operations or not. If you set this to true, then related entity - will be updated in the database if it was changed. -* `cascadeRemove?: boolean` - allow cascade remove operations or not. If you set this to true, then related entity - will be removed from the database if it was removed from this object. -* `onDelete?: string` - Database-level operation `ON DELETE` is an action that must be performed when related row in - the database has been removed. \ No newline at end of file diff --git a/docs/repository.md b/docs/repository.md deleted file mode 100644 index f453e54179..0000000000 --- a/docs/repository.md +++ /dev/null @@ -1,67 +0,0 @@ -## Repository - -For each entity you have there is a Repository for it. -Repository provides functionality to work with your entity. -Repository works the same way as EntityManager, but is more specific for concrete entity class. -There are several useful methods of the Repository: - -* `hasId(entity: Entity): boolean` - -Sometimes you want to check if your entity already has id or not. This maybe useful in situations when you want to -check if your object is new or not. - -* `create(plainJsObject?: Object): Entity` - -Creates a new empty instance of entity. If `plainJsObject` is given then new entity will be created and all properties -from the `plainJsObject` that can be mapped to this entity will be copied to the new entity. - -* `createMany(plainJsObjects: Object[]): Entity[]` - -Creates multiple new entities based on array of plain javascript objects. Properties for each plain javascript object -will be copied to newly created entities if they should exist there. - -* `initialize(object: Object): Promise` - -Creates a new entity from the given plan javascript object. If entity already exist in the database, then -it loads it (and everything related to it), replaces all values with the new ones from the given object -and returns this new entity. This new entity is actually a loaded from the db entity with all properties -replaced from the new object. - -* `persist(entity: Entity): Promise` - -Persists (saves) a given entity in the database. If entity does not exist in the database then it inserts it, -else if entity already exist in the database then it updates it. - -* `remove(entity: Entity): Promise` - -Removes a given entity from the database. - -* `find(conditions?: Object, options?: FindOptions): Promise` - -Finds entities that match given conditions or given find options. - -* `findOne(conditions?: Object, options?: FindOptions): Promise` - -Finds the first entity that match given conditions or given find options. - -* `findOneById(id: any, options?: FindOptions): Promise` - -Finds entity with a given entity id. - -* `findAndCount(conditions?: Object, options?: FindOptions): Promise<[Entity[], number]>` - -Finds entities that match given conditions or given find options plus gets a overall count of this items (for -pagination purposes). - -* `createQueryBuilder(alias: string): QueryBuilder` - -Creates a new query builder that can be used to build a sql query and get the results of the executed query. You can -learn more about query builder [here](docs/query-builder.md). - -* `query(sql: string): Promise` - -Executes raw SQL query. - -* `transaction(runInTransaction: () => Promise): Promise` - -Executes everything in the given function in a single transaction. diff --git a/docs/subscribers-and-entity-listeners.md b/docs/subscribers-and-entity-listeners.md deleted file mode 100644 index 30b7a9a61c..0000000000 --- a/docs/subscribers-and-entity-listeners.md +++ /dev/null @@ -1,127 +0,0 @@ -## Subscribers and Entity Listeners - -You can listen to events in the ORM. There two concepts you can use: - -* [Subscribers](#subscribers) -* [Entity Listeners](#entity-listeners) - -### Subscribers - -First you need to create a new subscriber class and implement `EventSubscriberInterface` interface: - -```typescript -import {EventSubscriber, UpdateEvent, RemoveEvent, InsertEvent} from "typeorm/listeners" -import {EventSubscriberInterface} from "typeorm"; - -@EventSubscriber() -export class MySubscriber implements EventSubscriberInterface { - - /** - * Called after entity insertion. - */ - beforeInsert(event: InsertEvent) { - console.log(`BEFORE ENTITY INSERTED: `, event.entity); - } - - /** - * Called after entity insertion. - */ - beforeUpdate(event: UpdateEvent) { - console.log(`BEFORE ENTITY UPDATED: `, event.entity); - } - - /** - * Called after entity insertion. - */ - beforeRemove(event: RemoveEvent) { - console.log(`BEFORE ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity); - } - - /** - * Called after entity insertion. - */ - afterInsert(event: InsertEvent) { - console.log(`AFTER ENTITY INSERTED: `, event.entity); - } - - /** - * Called after entity insertion. - */ - afterUpdate(event: UpdateEvent) { - console.log(`AFTER ENTITY UPDATED: `, event.entity); - } - - /** - * Called after entity insertion. - */ - afterRemove(event: RemoveEvent) { - console.log(`AFTER ENTITY WITH ID ${event.entityId} REMOVED: `, event.entity); - } - - /** - * Called after entity is loaded. - */ - afterLoad(entity: any) { - console.log(`AFTER ENTITY LOADED: `, entity); - } - -} -``` - -To register a subscriber you need to register it: - -```typescript -connectionManager.importSubscribers([MySubscriber]); -``` - -### Entity Listeners - -You can also use listeners in your entities. Such listeners can be convenient for a trivial operations. - -```typescript -import {Table} from "typeorm/tables"; -import {AfterLoad, AfterInsert, BeforeInsert, BeforeUpdate, AfterUpdate, BeforeRemove, AfterRemove} from "typeorm/listeners"; - -@Table("posts") -export class Post { - - // ... columns ... - - @AfterLoad() - generateRandomNumbers() { - console.log(`event: Post entity has been loaded and callback executed`); - } - - @BeforeInsert() - doSomethingBeforeInsertion() { - console.log("event: Post entity will be inserted so soon..."); - } - - @AfterInsert() - doSomethingAfterInsertion() { - console.log("event: Post entity has been inserted and callback executed"); - } - - @BeforeUpdate() - doSomethingBeforeUpdate() { - console.log("event: Post entity will be updated so soon..."); - } - - @AfterUpdate() - doSomethingAfterUpdate() { - console.log("event: Post entity has been updated and callback executed"); - } - - @BeforeRemove() - doSomethingBeforeRemove() { - console.log("event: Post entity will be removed so soon..."); - } - - @AfterRemove() - doSomethingAfterRemove() { - console.log("event: Post entity has been removed and callback executed"); - } - -} -``` - diff --git a/docs/table-columns.md b/docs/table-columns.md deleted file mode 100644 index 4cbd08a1fa..0000000000 --- a/docs/table-columns.md +++ /dev/null @@ -1,173 +0,0 @@ -## Table columns - -Entity consist of columns. For each entity column, column in the database will be created. - -* [@Column decorator](#@column) -* [@PrimaryColumn decorator](#@primary-column) -* [@CreateDateColumn decorator](#@create-date-column) -* [@UpdateDateColumn decorator](#@update-date-column) -* [Column types](#column-type) -* [Column options](#column-options) -* [Columns usage example](#example) - -#### @Column - -Column decorator simply marks an entity property to be a table column. -There are several column decorator signatures: - -```typescript -@Column(options?: ColumnOptions) -@Column(type?: ColumnType, options?: ColumnOptions) -``` - -#### @PrimaryColumn - -PrimaryColumn marks an entity property as a column and creates a primary key for it. -There are several column decorator signatures: - -```typescript -@PrimaryColumn(options?: ColumnOptions) -@PrimaryColumn(type?: ColumnType, options?: ColumnOptions) -``` - -#### @CreateDateColumn - -CreateDateColumn adds a simple datetime column to the table. During its first persistence (e.g. insertion) it -sets current date as a value of the property object. - -```typescript -@CreateDateColumn(options?: ColumnOptions) -``` - -#### @UpdateDateColumn - -UpdateDateColumn adds a simple datetime column to the table. Each time object is persisted, this column value is updated -to the current date. - -```typescript -@CreateDateColumn(options?: ColumnOptions) -``` - -#### ColumnType - -ColumnType can be one of: - -* `string` will be mapped to db's `varchar` -* `text` will be mapped to db's `text` -* `number` will be mapped to db's `double` -* `integer` will be mapped to db's `int` -* `int` will be mapped to db's `int` -* `smallint` will be mapped to db's `int` -* `bigint` will be mapped to db's `int` -* `float` will be mapped to db's `float` -* `double` will be mapped to db's `double` -* `decimal` will be mapped to db's `decimal` -* `date` will be mapped to db's `datetime` -* `time` will be mapped to db's `time` -* `datetime` will be mapped to db's `datetime` -* `timestamp` will be mapped to db's `timestamp` -* `boolean` will be mapped to db's `boolean` -* `json` will be mapped to db's `text` -* `simple_array` will be mapped to db's `text` - -If you omit a column type, type will be guessed automatically based on variable type: - -* `number` will be mapped to `float` -* `boolean` will be mapped to `boolean` -* `string` will be mapped to `varchar` -* `Date` will be mapped to `datetime` - -#### ColumnOptions - -ColumnOptions is an object with additional column options: - -* `name?: string` - column name in the database -* `type?: ColumnType` - column type also can be specified via column options -* `length?: string` - column type's length. For example type = "string" and length = 100 means that ORM will create a - column with type varchar(100). -* `generated?: boolean` - specifies if this column will use AUTO_INCREMENT or not (e.g. generated number) -* `unique?: boolean` - specifies if column's value must be unique or not. -* `nullable?: boolean` - indicates if column's value can be set to NULL. -* `columnDefinition?: string` - Extra column definition. Should be used only in emergency situations. -Note that if you'll use this property auto schema generation will not work properly anymore. -* `comment?: string` - column comment -* `precision?: number` - 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. -* `scale?: 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. -* `collation?: string` - Column collation. Note that not all databases support it. - -#### Example - -```typescript -@Table("photo") -class Photo { - - /** - * Primary column with auto increment key. - */ - @PrimaryColumn("int", { generated: true }) - id: number; - - /** - * Simple string column. - */ - @Column() - name: string; - - /** - * Simple boolean column. - */ - @Column() - isPublished: boolean; - - /** - * Simple numeric (float) column. - */ - @Column() - scale: number; - - /** - * Simple numeric (integer) column. - */ - @Column("integer") - size: number; - - /** - * Simple column that contains a date. - */ - @Column() - publishedDate: Date; - - /** - * Simple column that contains a big text. - */ - @Column("text") - description: string; - - /** - * Simple column that contains a short text. - */ - @Column({ - length: 3 - }) - locale: string; - - /** - * This column's value must be unique. - */ - @Column({ - unique: true - }) - slug: string; - - /** - * This column's value can be nullable. - */ - @Column({ - nullable: true - }) - metadata: string; - -} -``` diff --git a/docs/tables-and-table-inheritance.md b/docs/tables-and-table-inheritance.md deleted file mode 100644 index 175b434e46..0000000000 --- a/docs/tables-and-table-inheritance.md +++ /dev/null @@ -1,57 +0,0 @@ -## Tables and table inheritance - -### Tables - -ORM creates tables for each class that you decorated with `@Table` decorator and which you loaded into your -connection. - -```typescript -@Table("photos") -export class Photo { - -} -``` - -Each table must have a primary column (using `@PrimaryColumn` decotator) [*todo: really?*] and can contain -other [columns](table-columns.md). - -### Table inheritance - -If multiple tables has same properties you may want to find them a common abstraction and create a base -class for them. In this base class you'll have a common for all inherited classes columns. To ahieve this you -must mark your table with `@AbstractTable()` decorator: - -```typescript -@AbstractTable() -export class BasePhoto { - - @PrimaryColumn("int", { generated: true }) - id: string; - - @Column() - name: string; - - @Column() - url: string; - -} - -@Table("public_photos") -export class Photo extends BasePhoto { - - @Column() - authorName: string; - -} - -@Table("private_photos") -export class Photo extends BasePhoto { - - @Column() - isPublished: boolean; - -} -``` - -This will create you two tables `public_photos` and `private_photos` with 5 columns each. -Right now only columns are inherited. Relations are ignored. \ No newline at end of file diff --git a/docs/updating-database-schema.md b/docs/updating-database-schema.md deleted file mode 100644 index 7d87865a38..0000000000 --- a/docs/updating-database-schema.md +++ /dev/null @@ -1,17 +0,0 @@ -## Updating database schema - -Your database schema is managed automatically by ORM: - -* tables are created for all entities -* columns are created for all entity columns -* foreign keys are set for all relations -* junction tables are created for all many-to-many relations - -All this must be in sync to make ORM to work correctly. To make a synchronization there are two ways: - -* set in [connection options](connection-and-connection-options.md#connection-options) `autoSchemaSync: true`. -In this case database schema will be automatically synchronized each time you run the application. - -* use [schema update gulp plugin](todo) and run schema synchronization process each time you need it. - -First approach is not recommended to use in production, however it can be handy during development. \ No newline at end of file diff --git a/gulpfile.ts b/gulpfile.ts index 0cfc27f905..94e70cd570 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -164,7 +164,7 @@ export class Gulpfile { */ @Task() coveragePre() { - return gulp.src(["./build/es5/src/**/*.js"]) + return gulp.src(["./build/compiled/src/**/*.js"]) .pipe(istanbul()) .pipe(istanbul.hookRequire()); } @@ -178,7 +178,7 @@ export class Gulpfile { chai.use(require("sinon-chai")); chai.use(require("chai-as-promised")); - return gulp.src(["./build/es5/test/**/*.js"]) + return gulp.src(["./build/compiled/test/**/*.js"]) .pipe(mocha()) .pipe(istanbul.writeReports()); } diff --git a/ormconfig.json.dist b/ormconfig.json.dist new file mode 100644 index 0000000000..8342a4f3d6 --- /dev/null +++ b/ormconfig.json.dist @@ -0,0 +1,14 @@ +[ + { + "name": "default", + "driver": { + "type": "mysql", + "host": "localhost", + "port": 3306, + "username": "root", + "password": "admin", + "database": "test" + }, + "autoSchemaSync": true + } +] \ No newline at end of file diff --git a/package.json b/package.json index 99e9bb37f6..ad7fb2ae63 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "version": "0.0.2-alpha.67", "description": "Data-mapper ORM for Typescript", - "license": "Apache-2.0", + "license": "MIT", "readmeFilename": "README.md", "author": { "name": "Umed Khudoiberdiev", diff --git a/sample/sample2-one-to-one/app.ts b/sample/sample2-one-to-one/app.ts index 7af29a92ab..d1052646c3 100644 --- a/sample/sample2-one-to-one/app.ts +++ b/sample/sample2-one-to-one/app.ts @@ -10,9 +10,9 @@ import {PostAuthor} from "./entity/PostAuthor"; const options: ConnectionOptions = { driver: { - type: "postgres", + type: "mysql", host: "localhost", - port: 5432, + port: 3306, username: "root", password: "admin", database: "test" diff --git a/tsconfig.json b/tsconfig.json index 50a5aa9c74..a5179756c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { - "version": "2.0.2", + "version": "2.0.3", "compilerOptions": { "lib": ["es5", "es6", "dom"], - "outDir": "build/es5", + "outDir": "build/compiled", "target": "es6", "module": "commonjs", "moduleResolution": "node", @@ -18,6 +18,8 @@ "strictNullChecks": true }, "exclude": [ + "tmp", + "temp", "build", "node_modules" ]