master 4ce43f2cdcc2 cached
56 files
144.3 KB
35.4k tokens
134 symbols
1 requests
Download .txt
Repository: IRCraziestTaxi/typeorm-linq-repository
Branch: master
Commit: 4ce43f2cdcc2
Files: 56
Total size: 144.3 KB

Directory structure:
gitextract_ckorfcxu/

├── .gitignore
├── .typeorm/
│   ├── connection/
│   │   ├── get-migration-data-source.ts
│   │   ├── get-typeorm-data-source.config.ts
│   │   └── get-typeorm-data-source.function.ts
│   ├── migrations/
│   │   └── 1629340508553-Initial.ts
│   └── seed/
│       ├── functions/
│       │   ├── main.function.ts
│       │   ├── seed-artists.function.ts
│       │   ├── seed-genres.function.ts
│       │   ├── seed-songs.function.ts
│       │   ├── seed-user-profile-attributes.function.ts
│       │   └── seed-users.function.ts
│       └── index.ts
├── .vscode/
│   ├── launch.json
│   └── settings.json
├── LICENSE
├── README.md
├── index.ts
├── ormconfig.example.json
├── package.json
├── src/
│   ├── constants/
│   │   └── SqlConstants.ts
│   ├── enums/
│   │   ├── QueryMode.ts
│   │   └── QueryWhereType.ts
│   ├── query/
│   │   ├── Query.ts
│   │   ├── QueryBuilderPart.ts
│   │   └── interfaces/
│   │       ├── IComparableQuery.ts
│   │       ├── IComparableQueryBase.ts
│   │       ├── IJoinedComparableQuery.ts
│   │       ├── IJoinedQuery.ts
│   │       ├── IQuery.ts
│   │       ├── IQueryBase.ts
│   │       ├── IQueryBuilderPart.ts
│   │       ├── IQueryInternal.ts
│   │       ├── ISelectQuery.ts
│   │       └── ISelectQueryInternal.ts
│   ├── repository/
│   │   ├── LinqRepository.ts
│   │   └── interfaces/
│   │       └── ILinqRepository.ts
│   └── types/
│       ├── ComparableValue.ts
│       ├── EntityBase.ts
│       ├── EntityConstructor.ts
│       ├── JoinedEntityType.ts
│       ├── QueryConditionOptions.ts
│       ├── QueryConditionOptionsInternal.ts
│       ├── QueryOrderOptions.ts
│       └── RepositoryOptions.ts
├── test/
│   ├── entities/
│   │   ├── artist.entity.ts
│   │   ├── genre.entity.ts
│   │   ├── song.entity.ts
│   │   ├── user-profile-attribute.entity.ts
│   │   └── user.entity.ts
│   ├── jasmine-ts.helper.js
│   ├── jasmine.json
│   └── scenarios/
│       └── query/
│           └── query.spec.ts
├── tsconfig.build.json
├── tsconfig.json
├── tsconfig.spec.json
└── tslint.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
**/node_modules/**
**/*.log
**/*.js
!jasmine-ts.helper.js
**/*.d.ts
ormconfig.json
.DS_Store

================================================
FILE: .typeorm/connection/get-migration-data-source.ts
================================================
import { DataSource } from "typeorm";
import { dataSourceOptions } from "./get-typeorm-data-source.config";

const dataSource = new DataSource(dataSourceOptions);
dataSource.initialize();

export default dataSource;


================================================
FILE: .typeorm/connection/get-typeorm-data-source.config.ts
================================================
import { DataSourceOptions } from "typeorm";
import * as ormconfig from "../../ormconfig.json";
import { Artist } from "../../test/entities/artist.entity";
import { Genre } from "../../test/entities/genre.entity";
import { Song } from "../../test/entities/song.entity";
import { UserProfileAttribute } from "../../test/entities/user-profile-attribute.entity";
import { User } from "../../test/entities/user.entity";

export const dataSourceOptions: DataSourceOptions = {
    ...ormconfig as DataSourceOptions,
    entities: [
        Artist,
        Genre,
        Song,
        UserProfileAttribute,
        User
    ]
};


================================================
FILE: .typeorm/connection/get-typeorm-data-source.function.ts
================================================
import { DataSource } from "typeorm";
import { dataSourceOptions } from "./get-typeorm-data-source.config";

export async function getTypeormDataSource(): Promise<DataSource> {
    const dataSource = new DataSource(dataSourceOptions);
    await dataSource.initialize();

    return dataSource;
}


================================================
FILE: .typeorm/migrations/1629340508553-Initial.ts
================================================
import {MigrationInterface, QueryRunner} from "typeorm";

export class Initial1629340508553 implements MigrationInterface {
    name = 'Initial1629340508553'

    public async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query("CREATE TABLE `genre` (`id` int NOT NULL, `name` varchar(50) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB");
        await queryRunner.query("CREATE TABLE `song` (`artistId` int NOT NULL, `genreId` int NOT NULL, `id` int NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB");
        await queryRunner.query("CREATE TABLE `artist` (`id` int NOT NULL, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB");
        await queryRunner.query("CREATE TABLE `user` (`id` int NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB");
        await queryRunner.query("CREATE TABLE `user_profile_attribute` (`genreId` int NOT NULL, `id` int NOT NULL, `userId` int NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB");
        await queryRunner.query("ALTER TABLE `song` ADD CONSTRAINT `FK_fe76da76684ccb3d70d0f75994e` FOREIGN KEY (`artistId`) REFERENCES `artist`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION");
        await queryRunner.query("ALTER TABLE `song` ADD CONSTRAINT `FK_d9ffa20e72f9e6834680ead9fe4` FOREIGN KEY (`genreId`) REFERENCES `genre`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION");
        await queryRunner.query("ALTER TABLE `user_profile_attribute` ADD CONSTRAINT `FK_2e584d105f300ed4f325ba0a43c` FOREIGN KEY (`genreId`) REFERENCES `genre`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION");
        await queryRunner.query("ALTER TABLE `user_profile_attribute` ADD CONSTRAINT `FK_6689edd2d3aced2ac9510387a06` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE NO ACTION ON UPDATE NO ACTION");
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query("ALTER TABLE `user_profile_attribute` DROP FOREIGN KEY `FK_6689edd2d3aced2ac9510387a06`");
        await queryRunner.query("ALTER TABLE `user_profile_attribute` DROP FOREIGN KEY `FK_2e584d105f300ed4f325ba0a43c`");
        await queryRunner.query("ALTER TABLE `song` DROP FOREIGN KEY `FK_d9ffa20e72f9e6834680ead9fe4`");
        await queryRunner.query("ALTER TABLE `song` DROP FOREIGN KEY `FK_fe76da76684ccb3d70d0f75994e`");
        await queryRunner.query("DROP TABLE `user_profile_attribute`");
        await queryRunner.query("DROP TABLE `user`");
        await queryRunner.query("DROP TABLE `artist`");
        await queryRunner.query("DROP TABLE `song`");
        await queryRunner.query("DROP TABLE `genre`");
    }

}


================================================
FILE: .typeorm/seed/functions/main.function.ts
================================================
import { getTypeormDataSource } from "../../connection/get-typeorm-data-source.function";
import { seedArtists } from "./seed-artists.function";
import { seedGenres } from "./seed-genres.function";
import { seedSongs } from "./seed-songs.function";
import { seedUserProfileAttributes } from "./seed-user-profile-attributes.function";
import { seedUsers } from "./seed-users.function";

export async function main(): Promise<void> {
    console.log("Creating connection.");

    const dataSource = await getTypeormDataSource();

    await seedGenres();
    await seedArtists();
    await seedSongs();
    await seedUsers();
    await seedUserProfileAttributes();

    console.log("Closing connection.");

    await dataSource.destroy();
}


================================================
FILE: .typeorm/seed/functions/seed-artists.function.ts
================================================
import { LinqRepository } from "../../../src/repository/LinqRepository";
import { Artist } from "../../../test/entities/artist.entity";

export async function seedArtists(): Promise<void> {
    console.log("Seeding artists.");

    const artistRepository = new LinqRepository(Artist);

    const artists: Artist[] = [
        {
            id: 1,
            name: "Artist One"
        },
        {
            id: 2,
            name: "Artist Two"
        },
        {
            id: 3,
            name: "Artist Three"
        }
    ];

    await artistRepository.typeormRepository.insert(artists);

    console.log("Done seeding artists.");
}


================================================
FILE: .typeorm/seed/functions/seed-genres.function.ts
================================================
import { LinqRepository } from "../../../src/repository/LinqRepository";
import { Genre } from "../../../test/entities/genre.entity";

export async function seedGenres(): Promise<void> {
    console.log("Seeding genres.");

    const genreRepository = new LinqRepository(Genre);

    const genres: Genre[] = [
        {
            id: 1,
            name: "Rock"
        },
        {
            id: 2,
            name: "Hip Hop"
        },
        {
            id: 3,
            name: "Pop"
        }
    ];

    await genreRepository.typeormRepository.insert(genres);

    console.log("Done seeding genres.");
}


================================================
FILE: .typeorm/seed/functions/seed-songs.function.ts
================================================
import { LinqRepository } from "../../../src/repository/LinqRepository";
import { Song } from "../../../test/entities/song.entity";

export async function seedSongs(): Promise<void> {
    console.log("Seeding songs.");

    const songRepository = new LinqRepository(Song);

    const songs: Song[] = [
        {
            artistId: 1,
            genreId: 1,
            id: 1,
            name: "Rock Song"
        },
        {
            artistId: 1,
            genreId: 2,
            id: 2,
            name: "Hip Hop Song"
        },
        {
            artistId: 1,
            genreId: 3,
            id: 3,
            name: "Pop Song"
        },
        {
            artistId: 2,
            genreId: 1,
            id: 4,
            name: "Rock Song"
        },
        {
            artistId: 2,
            genreId: 2,
            id: 5,
            name: "Hip Hop Song"
        },
        {
            artistId: 3,
            genreId: 1,
            id: 6,
            name: "Rock Song"
        },
        {
            artistId: 3,
            genreId: 3,
            id: 7,
            name: "Pop Song"
        }
    ];

    await songRepository.typeormRepository.insert(songs);

    console.log("Done seeding songs.");
}


================================================
FILE: .typeorm/seed/functions/seed-user-profile-attributes.function.ts
================================================
import { LinqRepository } from "../../../src/repository/LinqRepository";
import { UserProfileAttribute } from "../../../test/entities/user-profile-attribute.entity";

export async function seedUserProfileAttributes(): Promise<void> {
    console.log("Seeding user profile attributes.");

    const userProfileAttributeRepository = new LinqRepository(UserProfileAttribute);

    const userProfileAttributes: UserProfileAttribute[] = [
        {
            genreId: 3,
            id: 1,
            userId: 1
        }
    ];

    await userProfileAttributeRepository.typeormRepository.insert(userProfileAttributes);

    console.log("Done seeding user profile attributes.");
}


================================================
FILE: .typeorm/seed/functions/seed-users.function.ts
================================================
import { LinqRepository } from "../../../src/repository/LinqRepository";
import { User } from "../../../test/entities/user.entity";

export async function seedUsers(): Promise<void> {
    console.log("Seeding users.");

    const userRepository = new LinqRepository(User);

    const users: User[] = [
        {
            id: 1
        }
    ];

    await userRepository.typeormRepository.insert(users);

    console.log("Done seeding users.");
}


================================================
FILE: .typeorm/seed/index.ts
================================================
import { main } from "./functions/main.function";

main()
    .then(() => {
        console.log("Done.");
    })
    .catch(error => {
        console.error("Error seeding database:");
        console.error(error);
    });


================================================
FILE: .vscode/launch.json
================================================
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Debug Spec",
            "program": "${workspaceFolder}/node_modules/.bin/jasmine",
            "args": [
                "--config=./test/jasmine.json"
            ]
        }
    ]
}

================================================
FILE: .vscode/settings.json
================================================
{
    "files.exclude": {
        "**/*.d.ts": true,
        "src/**/*.js": true,
        "index.js": true
    },
    "typescript.tsdk": "node_modules/typescript/lib"
}

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 IRCraziestTaxi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# typeorm-linq-repository
Wraps TypeORM repository pattern and QueryBuilder using fluent, LINQ-style queries.

## What's New
Two new libraries, [typeorm-linq-repository-testing](https://github.com/IRCraziestTaxi/typeorm-linq-repository-testing) and [typeorm-linq-repository-testing-nestjs](https://github.com/IRCraziestTaxi/typeorm-linq-repository-testing-nestjs), now make it easier to unit test `LinqRepository`.

[typeorm-linq-repository-testing-nestjs](https://github.com/IRCraziestTaxi/typeorm-linq-repository-testing-nestjs) contains a more "complete" example of usage since it has more practical usage in the context of another framework, but [typeorm-linq-repository-testing](https://github.com/IRCraziestTaxi/typeorm-linq-repository-testing) provides the raw components if you need to build something similar for another framework.

### Latest Changes
As of version 2.0.0:

Due to [breaking changes in TypeORM](https://github.com/typeorm/typeorm/issues/7428), namely the removal of `getConnectionManager`, the constructor for `LinqRepository` now requires the TypeORM `DataSource` in addition to the entity model. Likewise, since individually managed `DataSource` objects replace the `ConnectionManager` paradigm, `RepositoryOptions` now no longer contains the `connectionName` property.

### Older Changes
As of version 1.1.3:

A fix was implemented so that if a `where` with mapped properties was called that was not in the following format (without parentheses):

```ts
.where(e => e.relationshipOne.map(r => r.relationshipTwo))
```

an error would occur.

Now, the following is allowed (with parentheses):

```ts
.where((e) => e.relationshipOne.map((r) => r.relationshipTwo))
```

As of version 1.1.2:

A fix was implemented to make `from`, `inSelected`, and `notInSelected` compatible with entities that do not contain a property named `id` that is a `number`.

As of version 1.1.0:

A fix was implemented in which entities not implementing a property named `id` were not compatible with `LinqRepository`. To mitigate this, `id` was removed from the base `EntityBase` type. In addition:

* `RepositoryOptions` now allows you to specify the name of the entity's primary key in case it is not `id` so that `create` may still be used with the option `autoGenerateId` enabled and `getById` may be used. To do so:

```ts
new LinqRepository(Entity, {
    primaryKey: e => e.entityId
});
```

As of version 1.0.1:

A fix/improvement was implemented in which `include`d or `thenInclude`d relations may now be filtered by later using `join` or `thenJoin` along with a `where`. See the Filtering Included Relations section below.

As of version 1.0.0:

* The `update` method is now an alias for the new `upsert` method. This change was made to clarify that typeorm-linq-repository calls TypeORM's `save` method, which performs upserts on the provided entities. The `update` method was left in place to avoid breaking changes.

* The `ts-simple-nameof` dependency was updated to strip assertion operators from parsed property names in order to allow the following:

```ts
await fooRepository
    .getAll()
    .where(foo => foo.bar!.baz)
    .equal(value);
```

which is sometimes necessary when using strict null checks, for instance when a relationship is typed as optional/nullable.

* `LinqRepository` now exposes a `typeormRepository` property, which allows you to use the underlying TypeORM Repository if you need to access methods not available via the `createQueryBuilder` method. The `createQueryBuilder` method, although now redundant, was left in place to avoid breaking changes.

In version 1.0.0-alpha.23, a bug was fixed in which a call to the `where` method on a non-joined query with multiple joins in the property selector (i.e. `.where(p => p.comments.map(c => c.user.email))`) would use the wrong alias and throw an error.

As of version 1.0.0-alpha.22:

* Checking for existence or absence of relations in an array of relations (or existence or absense of relations that meet a certain condition) is now supported!

For example:

```ts
const accessiblePosts = await postRepository
    // Get posts where...
    .getAll()
    // Note: Must use groupBy method to check relations.
    .groupBy(p => p.id)
    // ...no tags exist...
    .whereNone(p => p.tags, t => t.id)
    // ...or the post contains the tag being searched for.
    .orAny(p => p.tags, t => t.id, t => t.id)
    .equal(tagId);
```

Notice the second argument in the `whereNone` and `orAny` methods. This argument simply serves as an arbitrary primitive property to `COUNT` relationships in the `HAVING` statement(s) resulting from those methods. This was done in order to not restrict entities to have a primary key named `id`; although that restriction would have conveniently shortened the signature of the method, not all schemas may name primary keys `id`.

See the Checking Relations section below.

* A bug was fixed in usage of the `where` method following the `include` or `thenInclude` methods. Previously, although the interface claimed that a `where` method following an `include` method operated on the query's base type, the query actually continued using the last included property type.

## Foreword
This is a work in progress. This project has just recently come out of alpha and should be treated as such. That being said, it has received some massive updates for a wide range of query complexity and is periodically being updated for bug fixes, so I hope it will continue to see lots of use and continue to mature.

`typeorm-linq-repository`'s queries handle simple includes, joins, and join conditions very well and now has the capability to take on more complex queries. The only way it will continue to mature is to have its limits tested see some issues and pull requests come in.

`typeorm-linq-repository` has been tested with Postgres and MySQL, but since TypeORM manages the ubiquity of queries amongst different database engines, it should work just fine with all database engines. Please feel free to give it a try and provide as much testing as possible for it!

## Prerequisites
[TypeORM](https://github.com/typeorm/typeorm "TypeORM"), a code-first relational database ORM for typescript, is the foundation of this project. If you are unfamiliar with TypeORM, I strongly suggest that you check it out.

## Installation
To add `typeorm-linq-repository` and its dependencies to your project using NPM:

```
npm install --save typeorm typeorm-linq-repository
```

## Linq Repository
`LinqRepository` is the repository that is constructed to interact with the table represented by the entity used as the type argument for the repository.

`LinqRepository` takes the TypeORM `DataSource` containing the connection to the entity's datbase and a class type representing a TypeORM model as its constructor argument.

```ts
import { LinqRepository } from "typeorm-linq-repository";
import { dataSource } from "path-to-initialized-data-source";
import { User } from "../../entities/User";

const userRepository: LinqRepository<User> = new LinqRepository(dataSource, User);
```

### Repository Options
To modify default behavior when setting up a repository, use `RepositoryOptions`.

Repository options include:

- `autoGenerateId`: A boolean value indicating whether the entity implements a primary key that is auto-generated. Default is `true`.
- `primaryKey`: A lambda function providing the entity's primary key property if it is not named `id`.

```ts
new LinqRepository(dataSource, Entity, {
    // This entity has a primary key that is not auto-generated.
    autoGenerateId: false,
    // This entity has a primary key whose name is not "id".
    primaryKey: e => e.entityId
});
```

Or as a repository extending `LinqRepository`:

```ts
import { DataSource } from "typeorm";
import { LinqRepository } from "typeorm-linq-repository";
import { Entity } from "../entities/Entity";

export class EntityRepository extends LinqRepository<Entity> {
    public constructor(dataSource: DataSource) {
        super(dataSource, Entity, {
            // This entity has a primary key that is not auto-generated.
            autoGenerateId: false,
            // This entity has a primary key whose name is not "id".
            primaryKey: e => e.entityId
        });
    }
}
```

### Injecting LinqRepository
Protip: You can easily make `LinqRepository` injectable! For example, using InversifyJS:

```ts
import { decorate, injectable, unmanaged } from "inversify";
import { LinqRepository } from "typeorm-linq-repository";

decorate(injectable(), LinqRepository);
decorate(unmanaged(), LinqRepository, 0);
decorate(unmanaged(), LinqRepository, 1);

export { LinqRepository };
```

### Injecting LinqRepository with NestJS
When creating injectable repositories extending `LinqRepository` in NestJS, you must use `@nestjs/typeorm`'s `InjectDataSource` decorator to inject a data source into your repository's constructor. Doing so forces Nest to wait until the TypeORM connection is established before continuing to construct the repository. If you do not use `InjectDataSource`, you will encounter errors because `LinqRepository` will try to get the entity's repository from the connection before it is established.

Here is an example of an injectable repository in NestJS:

```ts
import { Injectable } from "@nestjs/common";
import { InjectDataSource } from "@nestjs/typeorm";
import { DataSource } from "typeorm";
import { LinqRepository } from "typeorm-linq-repository";
import { Entity } from "./entity.entity";

@Injectable()
export class EntityRepository extends LinqRepository<Entity> {
    // NOTE: @InjectDataSource is required to force Nest to wait for the TypeORM connection to be established
    // before typeorm-linq-repository's LinqRepository attempts to get the repository from the connection.
    public constructor(
        @InjectDataSource(/* "data-source-name" or empty for "default" */)
        dataSource: DataSource
    ) {
        super(dataSource, Entity);
    }
}
```

You can see a working example of injecting repositories extending `LinqRepository` in NestJS [in this repository](https://github.com/IRCraziestTaxi/responsekit-nestjs-demo).

## Using Queries
`typeorm-linq-repository` not only makes setting up repositories incredibly easy; it also gives you powerful, LINQ-style query syntax.

### Retrieving Entities
You can query entities for all, many, or one result:

```ts
 // Gets all entities.
this._userRepository.getAll();

// Gets many entities.
this._userRepository
    .getAll()
    .where(u => u.admin)
    .isTrue();

// Gets one entity.
this._userRepository
    .getOne()
    .where(u => u.email)
    .equal(email);

// Finds one entity using its ID.
this._userRepository.getById(id);
```

### Counting Results
You may call `count()` on a query to get the count of records matching the current query conditions without killing the query as awaiting or calling `.then()` on the query otherwise would; this way, you can use a query to count all records matching a set of conditions and then set paging parameters on the same query.

For example:

```ts
let activeUserQuery = this._userRepository
    .getAll()
    .where(u => u.active)
    .isTrue();

// Count all active users.
const activeUserCount = await activeUserQuery.count();

// Set paging parameters on the query.
activeUserQuery = activeUserQuery
    .skip(skip)
    .take(take);

const pagedActiveUsers = await activeUserQuery;
```

### Type Safe Querying
This LINQ-style querying really shines by giving you type-safe includes, joins, and where statements, eliminating the need for hard-coded property names in query functions.

This includes conditional statements:

```ts
this._userRepository
    .getOne()
    .where(u => u.email)
    .equal(email);
```

As well as include statements:

```ts
this._userRepository
    .getById(id)
    .include(u => u.posts);
```

If the property `posts` ever changes, you get compile-time errors, ensuring the change is not overlooked in query statements.

### Multiple Includes
You can use `include()` more than once to include several properties on the query's base type:

```ts
this._userRepository
    .getById(id)
    .include(u => u.posts)
    .include(u => u.orders);
```

### Subsequent Includes and Current Property Type
Include statements transform the "current property type" on the Query so that subsequent `thenInclude()`s can be executed while maintaining this type safety.

```ts
this._userRepository
    .getById(id)
    .include(u => u.orders)
    .thenInclude(o => o.items);
```

```ts
this._userRepository
    .getById(id)
    .include(u => u.posts)
    .thenInclude(p => p.comments)
    .thenInclude(c => c.user);
```

You can use `include()` or `thenInclude()` on the same property more than once to subsequently include another relation without duplicating the include in the executed query.

```ts
this._userRepository
    .getById(id)
    .include(u => u.posts)
    .thenInclude(p => p.comments)
    .include(u => u.posts)
    .thenInclude(p => p.subscribedUsers);
```

### Filtering Results
Queries can be filtered on one or more conditions using `where()`, `and()`, and `or()`. Note that, just as with TypeORM's QueryBuilder, using `where()` more than once will overwrite previous `where()`s, so use `and()` and `or()` to add more conditions.

```ts
this._userRepository
    .getAll()
    .where(u => u.isActive)
    .isTrue()
    .and(u => u.lastLogin)
    .greaterThan(date);
```

Note also that this caveat only applies to "normal" where conditions; a where condition on a join is local to that join and does not affect any "normal" where conditions on a query.

```ts
this._postRepository
    .getAll()
    .join((p: Post) => p.user)
    .where((u: User) => u.id)
    .equal(id)
    .where((p: Post) => p.archived)
    .isTrue();
```

### Filtering Included Relations
To filter `include`d or `thenInclude`d relationships (which is not possible by using `.include(...).where(...)` since using `where` after `include` resets the query rather than performing a `where` on the `include`), use `join` or `thenJoin` after the `include` or `thenInclude`.

```ts
this._postRepository
    .getAll()
    .include(p => p.comments)
    // We want to exclude included comments based on conditions on replies
    // while not filtering any posts.
    .thenInclude(c => c.replies)
    // Therefore, use a joinAlso() here to maintain a LEFT JOIN on comments
    .joinAlso(p => p.comments)
    // but use a thenJoin() here to restrict comments and replies
    // to an INNER JOIN based on conditions.
    .thenJoin(c => c.replies)
    .where(r => r.user.email)
    .equal(filterEmail);
```

### Joined Properties in Comparisons
It is possible to join relationships on the fly during a conditional clause in order to compare a relationship's value.

```ts
this._postRepository
    .getAll()
    .where(p => p.date)
    .greaterThan(date)
    .and(p => p.user.id)
    .equal(userId);
```

In order to join through collections in this fashion, use the `Array.map()` method.

```ts
this._userRepository
    .getAll()
    .where(u => u.posts.map(p => p.comments.map(c => c.flagged)))
    .isTrue();
```

Note: If not already joined via one of the available `join` or `include` methods, relationships joined in this fashion will be joined as follows:

* `where()` and `and()` result in an `INNER JOIN`.
* `or()` results in a `LEFT JOIN`.

### Grouped (Bracketed) Conditional Clauses
In order to group conditional clauses into parentheses, use `isolatedWhere()`, `isolatedAnd()`, and `isolatedOr()`.

```ts
this._userRepository
    .getOne()
    .where(u => u.isAdmin)
    .isTrue()
    .isolatedOr(q => q
        .where(u => u.firstName)
        .equals("John")
        .and(u => u.lastName)
        .equals("Doe")
    ).isolatedOr(q => q
        .where(u => u.firstName)
        .equals("Jane")
        .and(u => u.lastName)
        .equals("Doe")
    );
```

### Comparing Basic Values
The following query conditions are available for basic comparisons:

`beginsWith(value: string)`: Finds results where the queried text begins with the supplied string.

`contains(value: string)`: Finds results were the queried text contains the supplied string.

`endsWith(value: string)`: Finds results where the queried text ends with the supplied string.

`equal(value: string | number | boolean)`: Finds results where the queried value is equal to the supplied value.

`greaterThan(value: number)`: Finds results where the queried value is greater than the supplied number.

`greaterThanOrEqual(value: number)`: Finds results where the queried value is greater than or equal to the supplied number.

`in(include: string[] | number[])`: Finds results where the queried value intersects the specified array of values to include.

`isFalse()`: Finds results where the queried boolean value is false.

`isNotNull()`: Finds results where the queried relation is not null.

`isNull()`: Finds results where the queried relation is null.

`isTrue()`: Finds results where the queried boolean value is true.

`lessThan(value: number)`: Finds results where the queried value is less than the supplied number.

`lessThanOrEqual(value: number)`: Finds results where the queried value is less than or equal to the supplied number.

`notEqual(value: string | number | boolean)`: Finds results where the queried value is not equal to the supplied value.

`notIn(exclude: string[] | number[])`: Finds results where the queried value intersects the specified array of values to exclude.

`inSelected()` and `notInSelected()` are also available and are covered later in this guide.

### String Comparison
When comparing strings, the default behavior is to not match case (case-insensitive comparison).

If a case-sensitive comparison is desired, use the `matchCase` option when executing a comparison.

```ts
// Perform a case-sensitive comparison rather than the default case-insensitive.
equal(value, { matchCase: true });
```

Note that, due to a lack of type reflection in JavaScript, the opposite is true for comparing values with joined entities. See the Comparing Values With Joined Entities section below.

### Inner Joins
Filter joined relations by using `where()`, `and()`, and `or()` on inner joins using `join()` and `thenJoin()`.

```ts
this._userRepository
    .getAll()
    .join(u => u.posts)
    .where(p => p.archived)
    .isTrue();

this._userRepository
    .getOne()
    .join(u => u.posts)
    .where(p => p.flagged)
    .isTrue()
    .and(p => p.date)
    .greaterThan(date);
```

Just as with `include()` and `thenInclude()`, `join()` always uses the query's base type, while `thenJoin()` continues to use the last joined entity's type.

```ts
this._postRepository
    .getAll()
    .join(p => p.user)
    .where(u => u.id)
    .equal(id)
    .thenJoin(u => u.comments)
    .where(c => c.flagged)
    .isTrue()
    .join(p => p.comments)
    .thenJoin(c => c.user)
    .where(u => u.dateOfBirth)
    .lessThan(date);
```

### Left Joins
As the above `join()` and `thenJoin()` perform an `INNER JOIN`, desired results may be lost if you wish to not exclude previously included results if the joined relations fail the join condition. Filter joined relations while not excluding previously included results by using `joinAlso()` and `thenJoinAlso()` to perform a `LEFT JOIN` instead.

### Regarding Included Relationships
Note that `.include()` and `.thenInclude()` are not intended to work the same way as `.join()`, `.joinAlso()`, `.thenJoin()`, and `.thenJoinAlso()` in conjunction with `.where()`.

That is, using `.include().where()` does NOT behave the same way as `.join().where()` (interpreted in "plain English" as "join where").

`.include()` and `.thenInclude()` were meant to stand alone in their own context rather than filtering the main entity based on joined relationships.

For example:

```ts
this._userRepository
    .getMany()
    // I want to include posts in my results, but I am filtering included posts without filtering user results.
    .include(u => u.posts)
    // However, I am also filtering on the user itself (.where() after .include() filters on the base type).
    .where(u => u.active)
    .isTrue();
```

On the other hand, if you do intend to include a relationship while also filtering results based on a condition on that included relationship, use `.include()` in conjunction with `.join().where()`, i.e.:

```ts
this._userRepository
    .getMany()
    // Include posts in results.
    .include(u => u.posts)
    // Use .join rather than .joinAlso to actually filter user results by post criteria.
    .join(u => u.posts)
    .where(p => p.archived)
    .isTrue();
```

Finally, if you intend to include a relationship while filtering those included relationships but not filtering out any entities of the base type, then use `.joinAlso().where()` in order to perform a `LEFT JOIN` as opposed to an `INNER JOIN`.

```ts
this._userRepository
    .getMany()
    // Include posts in results.
    .include(u => u.posts)
    // Use .joinAlso rather than .join to perform a left join to filter posts but not filter users.
    .joinAlso(u => u.posts)
    .where(p => p.archived)
    .isTrue();
```

### Joining Foreign Entities
Join from an unrelated entity using `from()`. A simple example of this is not easily provided, so see examples below for further guidance on using this method.

```ts
this._songRepository
    .getAll()
    .join(s => s.artist)
    .where(a => a.id)
    .equal(artistId)
    .from(UserProfileAttribute)
    .thenJoin(p => p.genre)
    // ...
```

### Comparing Values With Joined Entities
Perform comparisons with values on joined entities by calling `from()`, `join()`, and `thenJoin()` after calling `where()`, `and()`, or `or()`.

```ts
this._userRepository
    .getAll()
    .join(u => u.posts)
    .where(p => p.recordLikeCount)
    .thenJoin(p => p.category)
    .greaterThanJoined(c => c.averageLikeCount);
```

The following query conditions are available for comparisons on related entities' properties:

`equalJoined(selector: (obj: P) => any)`: Determines whether the property specified in the last "where" is equal to the specified property on the last joined entity.

`greaterThanJoined(selector: (obj: P) => any)`: Determines whether the property specified in the last "where" is less than the specified property on the last joined entity.

`greaterThanOrEqualJoined(selector: (obj: P) => any)`: Determines whether the property specified in the last "where" is greater than or equal to the specified property on the last joined entity.

`lessThanJoined(selector: (obj: P) => any)`: Determines whether the property specified in the last "where" is less than the specified property on the last joined entity.

`lessThanOrEqualJoined(selector: (obj: P) => any)`: Determines whether the property specified in the last "where" is less than or equal to the specified property on the last joined entity.

`notEqualJoined(selector: (obj: P) => any)`: Determines whether the property specified in the last "where" is not equal to the specified property on the last joined entity.

### String Comparison When Comparing Values With Joined Entities
Note that although non-joined string comparisons defaults to case-insensitive comparison, due to a lack of type reflection in JavaScript, the opposite is true for comparing values with joined entities. Therefore, the default behavior when using the above methods is to perform a case sensitive comparison, so you must specify `matchCase: false` when using the above methods if you wish to perform a case-insensitive comparison.

```ts
// Perform a case-insensitive comparison rather than the default case-sensitive when comparing joined entity's properties.
equalJoined(x => x.property, { matchCase: false });
```

### Checking Relations
It is possible to check for existence or absence of relations in an array of relations (or existence or absense of relations that meet a certain condition).

For example:

```ts
const accessiblePosts = await postRepository
    // Get posts where...
    .getAll()
    // Note: Must use groupBy method to check relations.
    .groupBy(p => p.id)
    // ...no tags exist (meaning the post is not restricted to a certain tag)...
    .whereNone(p => p.tags, t => t.id)
    // ...or the post contains the tag being searched for.
    .orAny(p => p.tags, t => t.id, t => t.id)
    .equal(tagId);
```

NOTE: As the underlying query executes methods that check relations as `HAVING COUNT(...)`, you MUST use the `groupBy` method to group results on an arbitrary primitive property of the query's base type; for instance, the primary key.

The following relation checking methods are available:

`whereAny`: Checks for existence of the specified relations; optionally checks for existence of relations meeting a criteria determined by the optional `conditionPropSelector` argument in conjunction with the following comparing method.

`whereNone`: Checks for absence of the specified relations; optionally checks for absence of relations meeting a criteria determined by the optional `conditionPropSelector` argument in conjunction with the following comparing method.

`andAny`: The same as `whereAny` but performed as `AND COUNT(...) > 0` (supplementing the initial `HAVING COUNT(...)`).

`andNone`: The same as `whereNone` but performed as `AND COUNT(...) = 0` (supplementing the initial `HAVING COUNT(...)`).

`orAny`: The same as `whereAny` but performed as `OR COUNT(...) > 0` (supplementing the initial `HAVING COUNT(...)`).

`orNone`: The same as `whereNone` but performed as `OR COUNT(...) = 0` (supplementing the initial `HAVING COUNT(...)`).

### Including or Excluding Results Within an Inner Query
To utilize an inner query, use the `inSelected()` and `notInSelected()` methods. Each takes an inner `ISelectQuery`, which is obtained by calling `select()` on the inner query after its construction and simply specifies which value to select from the inner query to project to the `IN` or `NOT IN` list.

The following example is overkill since, in reality, you would simply add the condition that the post is not archived on the main query, but consider what is going on within the queries in order to visualize how inner queries in `typeorm-linq-repository` work.

Consider a `PostRepository` from which we want to get all posts belonging to a certain user and only those that are not archived. The outer query in this instance gets all posts belonging to the specified user, while the inner query specified all posts that are not archived. The union of the two produces the results we want.

```ts
this._postRepository
    .getAll()
    .join(p => p.user)
    .where(u => u.id)
    .equal(id)
    .where(p => p.id)
    .inSelected(
        this._postRepository
            .getAll()
            .where(p => p.archived)
            .isFalse()
            .select(p => p.id)
    );
```

This next example is more representative of an actual situation in which an inner query is useful. Consider an application in which users set up a profile and add Profile Attributes which specify genres of songs they do NOT wish to hear; that is, the application would avoid songs with genres specified by the user's profile.

Given the following models:

`Artist.ts`
```ts
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { Song } from "./Song";

@Entity()
export class Artist {
    @PrimaryGeneratedColumn()
    public id: number;

    @Column({ nullable: false })
    public name: string;

    @OneToMany(() => Song, (song: Song) => song.artist)
    public songs: Song[];
}
```

`Genre.ts`
```ts
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { SongGenre } from "./SongGenre";

@Entity()
export class Genre {
    @PrimaryGeneratedColumn()
    public id: number;

    @Column({ nullable: false })
    public name: string;

    @OneToMany(() => SongGenre, (songGenre: SongGenre) => songGenre.genre)
    public songs: SongGenre[];
}
```

`Song.ts`
```ts
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { Artist } from "./Artist";
import { SongGenre } from "./SongGenre";

@Entity()
export class Song {
    @ManyToOne(() => Artist, (artist: Artist) => artist.songs)
    public artist: Artist;

    @OneToMany(() => SongGenre, (songGenre: SongGenre) => songGenre.song)
    public genres: SongGenre[];

    @PrimaryGeneratedColumn()
    public id: number;

    @Column({ nullable: false })
    public name: string;
}
```

`SongGenre.ts`
```ts
import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { Genre } from "./Genre";
import { Song } from "./Song";

/**
 * Links a song to a genre.
 */
@Entity()
export class SongGenre {
    @ManyToOne(() => Genre, (genre: Genre) => genre.songs)
    public genre: Genre;

    @PrimaryGeneratedColumn()
    public id: number;

    @ManyToOne(() => Song, (song: Song) => song.genres)
    public song: Song;
}
```

`User.ts`
```ts
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { UserProfileAttribute } from "./UserProfileAttribute";

@Entity()
export class User {
    @Column({ nullable: false })
    public email: string;

    @PrimaryGeneratedColumn()
    public id: number;

    @Column({ nullable: false })
    public password: string;

    @OneToMany(() => UserProfileAttribute, (profileAttribute: UserProfileAttribute) => profileAttribute.user)
    public profile: UserProfileAttribute[];
}
```

`UserProfileAttribute.ts`
```ts
import { Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { Genre } from "./Genre";
import { User } from "./User";

/**
 * An attribute of a user's profile specifying a genre that user does not wish to hear.
 */
@Entity()
export class UserProfileAttribute {
    @ManyToOne(() => Genre)
    public genre: Genre;

    @PrimaryGeneratedColumn()
    public id: number;

    @ManyToOne(() => User, (user: User) => user.profile)
    public user: User;
}
```

Now, consider the following query from which we want to gather all songs by a certain artist that a certain user wants to hear; that is, songs by that artist that do not match a genre blocked by the user's profile.

```ts
this._songRepository
    .getAll()
    .join(s => s.artist)
    .where(a => a.id)
    .equal(artistId)
    .where(s => s.id)
    .notInSelected(
        this._songRepository
            .getAll()
            .join(s => s.artist)
            .where(a => a.id)
            .equal(artistId)
            .from(UserProfileAttribute)
            .thenJoin(p => p.genre)
            .where(g => g.id)
            .join(s => s.songGenre)
            .thenJoin(sg => sg.genre)
            .equalJoined(g => g.id)
            .from(UserProfileAttribute)
            .thenJoin(p => p.user)
            .where(u => u.id)
            .equal(userId)
            .select(s => s.id)
    );
```

### Selection Type
Calling `select()` after completing any comparison operations uses the query's base type. If you wish to select a property from a relation rather than the query's base type, you may call `select()` after one or more joins on the query.

```ts
this._songRepository
    .getAll()
    .join(s => s.genres)
    .thenJoin(sg => sg.genre)
    .select(g => g.id);
```

### Ordering Queries
You can order queries in either direction and using as many subsequent order statements as needed.

```ts
this._userRepository
    .getAll()
    .orderBy(u => u.lastName)
    .thenBy(u => u.firstName);
```

You can use include statements to change the query's property type and order on properties of that child.

```ts
this._userRepository
    .getAll()
    .orderByDescending(u => u.email)
    .include(u => u.posts)
    .thenByDescending(p => p.date);
```

### Grouping Results
You can group results by one or more properties using `groupBy` and `thenGroupBy`.

```ts
this._userRepository
    .getAll()
    .groupBy(u => u.lastName)
    .thenGroupBy(u => u.firstName);
```

### Using Query Results
Queries are transformed into promises whenever you are ready to consume the results.

Queries can be returned as raw promises:

```ts
this._userRepository
    .getById(id)
    .toPromise();
```

Or invoked as a promise on the spot:

```ts
this._userRepository
    .getById(id)
    .then(user => {
        // ...
    });
```

Or, using ES6 async syntax:

```ts
const user = await this._userRepository.getById(user);
```

### Using TypeORM's Query Builder
If you encounter an issue or a query which this query wrapper cannot accommodate, you can use TypeORM's native QueryBuilder.

```ts
this._userRepository.createQueryBuilder("user");
```

## Persisting Entities
The following methods persist and remove entities from the database:

```ts
// Creates one or more entities.
create(entities: T | T[]): Promise<T | T[]>;

// Deletes one or more entities by reference or one entity by ID.
delete(entities: number | string | T | T[]): Promise<boolean>;

// Updates one or more entities.
update(entities: T | T[]): Promise<T | T[]>;
```

## Transaction support
This library was unfortunately developed without regard to transactions, but another library called [typeorm-transactional-cls-hooked](https://github.com/odavid/typeorm-transactional-cls-hooked) makes utilizing transations extremely easy!

To use this library in conjuntion with `typeorm-linq-repository`, install `typeorm-transactional-cls-hooked` along with its dependencies:

```
npm install --save typeorm-transactional-cls-hooked cls-hooked

npm install --save-dev @types/cls-hooked
```

Then, per `typeorm-transactional-cls-hooked`'s documentation, simply patch TypeORM's repository with `typeorm-transactional-cls-hooked`'s base repository when bootstrapping your app:

```ts
import { initializeTransactionalContext, patchTypeORMRepositoryWithBaseRepository } from "typeorm-transactional-cls-hooked";

// Initialize cls-hooked.
initializeTransactionalContext();
// Patch TypeORM's Repository with typeorm-transactional-cls-hooked's BaseRepository.
patchTypeORMRepositoryWithBaseRepository();
```

That's it! Now all you need to do is use `typeorm-transactional-cls-hooked`'s `@Transactional()` decorator on methods that persist entities to your repositories. See `typeorm-transactional-cls-hooked`'s docs for more details.

### Unit testing
Two libraries, [typeorm-linq-repository-testing](https://github.com/IRCraziestTaxi/typeorm-linq-repository-testing) and [typeorm-linq-repository-testing-nestjs](https://github.com/IRCraziestTaxi/typeorm-linq-repository-testing-nestjs), now make it easier to unit test `LinqRepository`.

[typeorm-linq-repository-testing-nestjs](https://github.com/IRCraziestTaxi/typeorm-linq-repository-testing-nestjs) contains a more "complete" example of usage since it has more practical usage in the context of another framework, but [typeorm-linq-repository-testing](https://github.com/IRCraziestTaxi/typeorm-linq-repository-testing) provides the raw components if you need to build something similar for another framework.


================================================
FILE: index.ts
================================================
export { IComparableQuery } from "./src/query/interfaces/IComparableQuery";
export { IJoinedComparableQuery } from "./src/query/interfaces/IJoinedComparableQuery";
export { IJoinedQuery } from "./src/query/interfaces/IJoinedQuery";
export { IQuery } from "./src/query/interfaces/IQuery";
export { ISelectQuery } from "./src/query/interfaces/ISelectQuery";
export { ILinqRepository } from "./src/repository/interfaces/ILinqRepository";
export { LinqRepository } from "./src/repository/LinqRepository";
export { RepositoryOptions } from "./src/types/RepositoryOptions";


================================================
FILE: ormconfig.example.json
================================================
{
    "database": "typeorm-linq-repository-test",
    "host": "localhost",
    "migrations": [
        ".typeorm/migrations/*.ts"
    ],
    "password": "root",
    "port": 3306,
    "type": "mysql",
    "username": "root"
}

================================================
FILE: package.json
================================================
{
  "name": "typeorm-linq-repository",
  "version": "2.0.2",
  "description": "Wraps TypeORM repository pattern and QueryBuilder using fluent, LINQ-style queries.",
  "main": "index.js",
  "scripts": {
    "db:seed": "ts-node ./.typeorm/seed/index.ts",
    "mig:make": "npm run typeorm:registered -- migration:generate -d \"./.typeorm/connection/get-migration-data-source.ts\"",
    "mig:revert": "npm run typeorm:registered -- migration:revert -d \"./.typeorm/connection/get-migration-data-source.ts\"",
    "mig:run": "npm run typeorm:registered -- migration:run -d \"./.typeorm/connection/get-migration-data-source.ts\"",
    "test": "jasmine --config=\"./test/jasmine.json\"",
    "tsc": "node_modules/.bin/tsc -d --project \"./tsconfig.build.json\"",
    "tslint": "node_modules/.bin/tslint --project .",
    "typeorm:registered": "ts-node ./node_modules/typeorm/cli.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/IRCraziestTaxi/typeorm-linq-repository.git"
  },
  "keywords": [
    "typeorm",
    "repository",
    "linq",
    "query"
  ],
  "author": "IRCraziestTaxi",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/IRCraziestTaxi/typeorm-linq-repository/issues"
  },
  "homepage": "https://github.com/IRCraziestTaxi/typeorm-linq-repository#readme",
  "dependencies": {
    "ts-simple-nameof": "^1.3.1"
  },
  "devDependencies": {
    "@types/jasmine": "^5.1.4",
    "@types/node": "^20.11.0",
    "jasmine": "^5.1.0",
    "mysql": "^2.18.1",
    "ts-node": "^10.9.2",
    "tslint": "6.1.3",
    "typeorm": "^0.3.19",
    "typescript": "^5.3.3"
  },
  "peerDependencies": {
    "typeorm": "^0.3.19"
  },
  "files": [
    "package.json",
    "LICENSE",
    "index.js",
    "index.d.ts",
    "src/**/*.js",
    "src/**/*.d.ts"
  ]
}


================================================
FILE: src/constants/SqlConstants.ts
================================================
export class SqlConstants {
    public static readonly OPERATOR_AND: "AND" = "AND";
    public static readonly OPERATOR_EQUAL: "=" = "=";
    public static readonly OPERATOR_GREATER: ">" = ">";
    public static readonly OPERATOR_GREATER_EQUAL: ">=" = ">=";
    public static readonly OPERATOR_IN: "IN" = "IN";
    public static readonly OPERATOR_IS: "IS" = "IS";
    public static readonly OPERATOR_LESS: "<" = "<";
    public static readonly OPERATOR_LESS_EQUAL: "<=" = "<=";
    public static readonly OPERATOR_LIKE: "LIKE" = "LIKE";
    public static readonly OPERATOR_NOT_EQUAL: "!=" = "!=";
    public static readonly OPERATOR_NOT_IN: "NOT IN" = "NOT IN";
    public static readonly OPERATOR_NOT_NULL: "NOT NULL" = "NOT NULL";
    public static readonly OPERATOR_NULL: "NULL" = "NULL";
    public static readonly OPERATOR_OR: "OR" = "OR";
}

================================================
FILE: src/enums/QueryMode.ts
================================================
export enum QueryMode {
    /**
     * The default mode of a query in which results are returned.
     */
    Get = 0,
    /**
     * The mode of a query in which a relation is joined or included.
     */
    Join = 1,
    /**
     * The mode of a query in which a comparison is being made.
     */
    Compare = 2
}

================================================
FILE: src/enums/QueryWhereType.ts
================================================
export enum QueryWhereType {
    /**
     * A normal comparison (not on a joined entity).
     */
    Normal = 0,
    /**
     * A comparison involving a joined entity.
     */
    Joined = 1
}

================================================
FILE: src/query/Query.ts
================================================
import { nameof } from "ts-simple-nameof";
import { Brackets, ObjectLiteral, SelectQueryBuilder, WhereExpression } from "typeorm";
import { SqlConstants } from "../constants/SqlConstants";
import { QueryMode } from "../enums/QueryMode";
import { QueryWhereType } from "../enums/QueryWhereType";
import { ComparableValue } from "../types/ComparableValue";
import { EntityBase } from "../types/EntityBase";
import { JoinedEntityType } from "../types/JoinedEntityType";
import { QueryConditionOptions } from "../types/QueryConditionOptions";
import { QueryConditionOptionsInternal } from "../types/QueryConditionOptionsInternal";
import { QueryOrderOptions } from "../types/QueryOrderOptions";
import { IComparableQuery } from "./interfaces/IComparableQuery";
import { IJoinedComparableQuery } from "./interfaces/IJoinedComparableQuery";
import { IJoinedQuery } from "./interfaces/IJoinedQuery";
import { IQuery } from "./interfaces/IQuery";
import { IQueryBuilderPart } from "./interfaces/IQueryBuilderPart";
import { IQueryInternal } from "./interfaces/IQueryInternal";
import { ISelectQuery } from "./interfaces/ISelectQuery";
import { ISelectQueryInternal } from "./interfaces/ISelectQueryInternal";
import { QueryBuilderPart } from "./QueryBuilderPart";

export class Query<T extends EntityBase, R extends T | T[], P = T>
    implements IQuery<T, R, P>, IJoinedQuery<T, R, P>,
    IComparableQuery<T, R, P>,
    IJoinedComparableQuery<T, R, P>,
    IQueryInternal<T, R, P>,
    ISelectQueryInternal<T, R, P> {
    private readonly _duplicateAliasHistory: string[];
    private readonly _getAction: () => Promise<R>;
    private readonly _includeAliasHistory: string[];
    private readonly _initialAlias: string;
    private readonly _query: SelectQueryBuilder<T>;
    private readonly _queryParts: IQueryBuilderPart<T>[];

    private _lastAlias: string;
    private _queryMode: QueryMode;
    private _queryWhereType: QueryWhereType;
    private _selectedProperty: string;

    /**
     * Constructs a Query wrapper.
     * @param queryBuilder The QueryBuilder to wrap.
     * @param getAction Either queryBuilder.getOne or queryBuilder.getMany.
     */
    public constructor(
        queryBuilder: SelectQueryBuilder<T>,
        getAction: () => Promise<R>,
        includeAliasHistory: string[] = []
    ) {
        this._duplicateAliasHistory = [];
        this._getAction = getAction;
        this._includeAliasHistory = includeAliasHistory;
        this._initialAlias = queryBuilder.alias;
        this._query = queryBuilder;
        this._queryParts = [];

        this._lastAlias = this._initialAlias;
        this._queryMode = QueryMode.Get;
        this._queryWhereType = QueryWhereType.Normal;
        this._selectedProperty = "";
    }

    public get getAction(): () => Promise<R> {
        return this._getAction;
    }

    public get query(): SelectQueryBuilder<T> {
        return this._query;
    }

    public get queryParts(): IQueryBuilderPart<T>[] {
        return this._queryParts;
    }

    public get selected(): string {
        return this._selectedProperty;
    }

    public and<S extends Object, PP = P, RR = P>(propertySelector: (obj: PP) => S): IComparableQuery<T, R, RR> {
        return this.andOr(propertySelector, SqlConstants.OPERATOR_AND, this._query.andWhere);
    }

    // TODO: Can we use something besides the VERY unfortunate "any" here?
    // With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
    // TS is complaining about IComparableQuery not having a whole bunch of methods
    // from IQuery (which is true).
    public andAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector?: (obj: S) => ComparableValue
    ): any {
        return this.relationCount(
            relationSelector,
            relationCountPropSelector,
            this._query.andHaving,
            ">",
            conditionPropSelector
        );
    }

    // TODO: Can we use something besides the VERY unfortunate "any" here?
    // With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
    // TS is complaining about IComparableQuery not having a whole bunch of methods
    // from IQuery (which is true).
    public andNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector?: (obj: S) => ComparableValue
    ): any {
        return this.relationCount(
            relationSelector,
            relationCountPropSelector,
            this._query.andHaving,
            "=",
            conditionPropSelector
        );
    }

    public beginsWith(value: string, options?: QueryConditionOptions): IQuery<T, R, P> {
        return this.completeWhere(
            SqlConstants.OPERATOR_LIKE,
            value,
            {
                beginsWith: true
            },
            options
        );
    }

    public async catch(rejected: (error: any) => void | Promise<any> | IQuery<any, any>): Promise<any> {
        return this.toPromise()
            .catch(rejected);
    }

    public contains(value: string, options?: QueryConditionOptions): IQuery<T, R, P> {
        return this.completeWhere(
            SqlConstants.OPERATOR_LIKE,
            value,
            {
                beginsWith: true,
                endsWith: true
            },
            options
        );
    }

    public count(): Promise<number> {
        const targetQueryBuilder = this._query.clone();
        this.compileQueryParts(this._queryParts, targetQueryBuilder);

        return targetQueryBuilder.getCount();
    }

    public endsWith(value: string, options?: QueryConditionOptions): IQuery<T, R, P> {
        return this.completeWhere(
            SqlConstants.OPERATOR_LIKE,
            value,
            {
                endsWith: true
            },
            options
        );
    }

    public equal(value: ComparableValue, options?: QueryConditionOptions): IQuery<T, R, P> {
        return this.completeWhere(SqlConstants.OPERATOR_EQUAL, value, null, options);
    }

    public equalJoined(selector: (obj: P) => any, options?: QueryConditionOptions): IQuery<T, R, P> {
        return this.completeJoinedWhere(SqlConstants.OPERATOR_EQUAL, selector, options);
    }

    // <any> is necessary here because the usage of this method
    // depends on the interface from which it was called.
    public from<F extends EntityBase>(
        foreignEntity: { new(...params: any[]): F; }
    ): IJoinedQuery<T, R, F> | IComparableQuery<T, R, F> | any {
        return this.joinForeignEntity(foreignEntity);
    }

    public greaterThan(value: number | Date): IQuery<T, R, P> {
        return this.completeWhere(SqlConstants.OPERATOR_GREATER, value);
    }

    public greaterThanJoined(selector: (obj: P) => any): IQuery<T, R, P> {
        return this.completeJoinedWhere(SqlConstants.OPERATOR_GREATER, selector);
    }

    public greaterThanOrEqual(value: number | Date): IQuery<T, R, P> {
        return this.completeWhere(SqlConstants.OPERATOR_GREATER_EQUAL, value);
    }

    public greaterThanOrEqualJoined(selector: (obj: P) => any): IQuery<T, R, P> {
        return this.completeJoinedWhere(SqlConstants.OPERATOR_GREATER_EQUAL, selector);
    }

    public groupBy(propertySelector: (obj: P) => any): IQuery<T, R, P> {
        const propertyName: string = nameof<P>(propertySelector);
        const groupProperty: string = `${this._lastAlias}.${propertyName}`;

        return this.completeGroupBy(
            this._query.groupBy,
            groupProperty
        );
    }

    public in(include: string[] | number[], options?: QueryConditionOptions): IQuery<T, R, P> {
        // If comparing strings, must escape them as strings in the query.
        this.escapeStringArray(include as string[]);

        return this.completeWhere(
            SqlConstants.OPERATOR_IN,
            `(${include.join(", ")})`,
            { quoteString: false },
            options
        );
    }

    public include<S>(propertySelector: (obj: T) => JoinedEntityType<S>): IQuery<T, R, S> {
        return this.includePropertyUsingAlias<S>(propertySelector, this._initialAlias);
    }

    public inSelected<TI extends EntityBase, RI extends TI | TI[], PI1 = TI>(
        innerQuery: ISelectQuery<TI, RI, PI1>
    ): IQuery<T, R, P> {
        return this.includeOrExcludeFromInnerQuery(
            <ISelectQueryInternal<TI, RI, PI1>>innerQuery,
            SqlConstants.OPERATOR_IN
        );
    }

    public isFalse(): IQuery<T, R, P> {
        this.equal(false);

        return this;
    }

    public isNotNull(): IQuery<T, R, P> {
        return this.completeWhere(
            SqlConstants.OPERATOR_IS,
            SqlConstants.OPERATOR_NOT_NULL,
            { quoteString: false }
        );
    }

    public isNull(): IQuery<T, R, P> {
        return this.completeWhere(
            SqlConstants.OPERATOR_IS,
            SqlConstants.OPERATOR_NULL,
            { quoteString: false }
        );
    }

    public isolatedAnd<S extends Object>(and: (query: IQuery<T, R, P>) => IQuery<T, R, S>): IQuery<T, R, P> {
        // TODO: These types are not lining up.
        return <IQuery<T, R, P>><any>this.isolatedConditions<T, P>(
            <() => IQuery<T, R, P>><any>and,
            this._query.andWhere
        );
    }

    public isolatedOr<S extends Object>(and: (query: IQuery<T, R, P>) => IQuery<T, R, S>): IQuery<T, R, P> {
        // TODO: These types are not lining up.
        return <IQuery<T, R, P>><any>this.isolatedConditions<T, P>(
            <() => IQuery<T, R, P>><any>and,
            this._query.orWhere
        );
    }

    public isolatedWhere<S extends Object>(
        where: (query: IQuery<T, R, T>) => IQuery<T, R, S>
    ): IQuery<T, R, T> {
        // TODO: These types are not lining up.
        return this.isolatedConditions<T, S>(<() => IQuery<T, R, S>><any>where, this._query.where);
    }

    public isTrue(): IQuery<T, R, P> {
        this.equal(true);

        return this;
    }

    public join<S extends Object>(
        propertySelector: (obj: T) => JoinedEntityType<S>
    ): IQuery<T, R, S> | IComparableQuery<T, R, S> | any {
        return this.joinPropertyUsingAlias(propertySelector, this._initialAlias);
    }

    public joinAlso<S extends Object>(
        propertySelector: (obj: T) => JoinedEntityType<S>
    ): IQuery<T, R, S> | IComparableQuery<T, R, S> | any {
        return this.joinPropertyUsingAlias(propertySelector, this._initialAlias, this._query.leftJoin);
    }

    public lessThan(value: number | Date): IQuery<T, R, P> {
        return this.completeWhere(SqlConstants.OPERATOR_LESS, value);
    }

    public lessThanJoined(selector: (obj: P) => any): IQuery<T, R, P> {
        return this.completeJoinedWhere(SqlConstants.OPERATOR_LESS, selector);
    }

    public lessThanOrEqual(value: number | Date): IQuery<T, R, P> {
        return this.completeWhere(SqlConstants.OPERATOR_LESS_EQUAL, value);
    }

    public lessThanOrEqualJoined(selector: (obj: P) => any): IQuery<T, R, P> {
        return this.completeJoinedWhere(SqlConstants.OPERATOR_LESS_EQUAL, selector);
    }

    public notEqual(
        value: ComparableValue, options?: QueryConditionOptions
    ): IQuery<T, R, P> {
        return this.completeWhere(SqlConstants.OPERATOR_NOT_EQUAL, value, null, options);
    }

    public notEqualJoined(selector: (obj: P) => any, options?: QueryConditionOptions): IQuery<T, R, P> {
        return this.completeJoinedWhere(SqlConstants.OPERATOR_NOT_EQUAL, selector, options);
    }

    public notIn(exclude: string[] | number[], options?: QueryConditionOptions): IQuery<T, R, P> {
        // If comparing strings, must escape them as strings in the query.
        this.escapeStringArray(exclude as string[]);

        return this.completeWhere(
            SqlConstants.OPERATOR_NOT_IN,
            `(${exclude.join(", ")})`,
            { quoteString: false },
            options
        );
    }

    public notInSelected<TI extends EntityBase, RI extends TI | TI[], PI1 = TI>(
        innerQuery: ISelectQuery<TI, RI, PI1>
    ): IQuery<T, R, P> {
        return this.includeOrExcludeFromInnerQuery(
            <ISelectQueryInternal<TI, RI, PI1>>innerQuery,
            SqlConstants.OPERATOR_NOT_IN
        );
    }

    public or<S extends Object>(propertySelector: (obj: P) => S): IComparableQuery<T, R, P> {
        return this.andOr(propertySelector, SqlConstants.OPERATOR_OR, this._query.orWhere);
    }

    // TODO: Can we use something besides the VERY unfortunate "any" here?
    // With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
    // TS is complaining about IComparableQuery not having a whole bunch of methods
    // from IQuery (which is true).
    public orAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector?: (obj: S) => ComparableValue
    ): any {
        return this.relationCount(
            relationSelector,
            relationCountPropSelector,
            this._query.orHaving,
            ">",
            conditionPropSelector
        );
    }

    public orderBy(propertySelector: (obj: P) => any, options?: QueryOrderOptions): IQuery<T, R, P> {
        const propertyName: string = nameof<P>(propertySelector);
        const orderProperty: string = `${this._lastAlias}.${propertyName}`;

        return this.completeOrderBy(
            this._query.orderBy,
            [orderProperty, "ASC"],
            options
        );
    }

    public orderByDescending(
        propertySelector: (obj: P) => any, options?: QueryOrderOptions
    ): IQuery<T, R, P> {
        const propertyName: string = nameof<P>(propertySelector);
        const orderProperty: string = `${this._lastAlias}.${propertyName}`;

        return this.completeOrderBy(
            this._query.orderBy,
            [orderProperty, "DESC"],
            options
        );
    }

    // TODO: Can we use something besides the VERY unfortunate "any" here?
    // With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
    // TS is complaining about IComparableQuery not having a whole bunch of methods
    // from IQuery (which is true).
    public orNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector?: (obj: S) => ComparableValue
    ): any {
        return this.relationCount(
            relationSelector,
            relationCountPropSelector,
            this._query.orHaving,
            "=",
            conditionPropSelector
        );
    }

    public reset(): IQuery<T, R, T> {
        this._lastAlias = this._initialAlias;
        // Exit the "join chain" so that additional comparisons may be made on the base entity.
        this._queryWhereType = QueryWhereType.Normal;

        return <IQuery<T, R, T>><any>this;
    }

    public select(propertySelector: (obj: any) => any): ISelectQuery<T, R, T> | ISelectQuery<T, R, P> {
        const selectedProperty: string = nameof(propertySelector);
        let alias: string = null;

        // If coming out of a comparison, query is back in "base mode" (where and select use base type).
        if (this._queryMode === QueryMode.Get) {
            alias = this._initialAlias;
        }
        // If in a join, use the last joined entity to select a property.
        else {
            alias = this._lastAlias;
        }

        this._selectedProperty = `${alias}.${selectedProperty}`;

        return this;
    }

    public skip(skip: number): IQuery<T, R, P> {
        if (skip > 0) {
            this._queryParts.push(new QueryBuilderPart(
                this._query.skip, [skip]
            ));
        }

        return this;
    }

    public take(limit: number): IQuery<T, R, P> {
        if (limit > 0) {
            this._queryParts.push(new QueryBuilderPart(
                this._query.take, [limit]
            ));
        }

        return this;
    }

    public async then(resolved: (results: R) => void | Promise<any>): Promise<any> {
        return this.toPromise()
            .then(resolved);
    }

    public thenBy(propertySelector: (obj: P) => any, options?: QueryOrderOptions): IQuery<T, R, P> {
        const propertyName: string = nameof<P>(propertySelector);
        const orderProperty: string = `${this._lastAlias}.${propertyName}`;

        return this.completeOrderBy(
            this._query.addOrderBy,
            [orderProperty, "ASC"],
            options
        );
    }

    public thenByDescending(
        propertySelector: (obj: P) => any,
        options?: QueryOrderOptions
    ): IQuery<T, R, P> {
        const propertyName: string = nameof<P>(propertySelector);
        const orderProperty: string = `${this._lastAlias}.${propertyName}`;

        return this.completeOrderBy(
            this._query.addOrderBy,
            [orderProperty, "DESC"],
            options
        );
    }

    public thenGroupBy(propertySelector: (obj: P) => any): IQuery<T, R, P> {
        const propertyName: string = nameof<P>(propertySelector);
        const groupProperty: string = `${this._lastAlias}.${propertyName}`;

        return this.completeGroupBy(
            this._query.addGroupBy,
            groupProperty
        );
    }

    public thenInclude<S extends Object>(
        propertySelector: (obj: P) => JoinedEntityType<S>
    ): IQuery<T, R, S> {
        return this.includePropertyUsingAlias<S>(propertySelector, this._lastAlias);
    }

    public thenJoin<S extends Object>(
        propertySelector: (obj: P) => JoinedEntityType<S>
    ): IQuery<T, R, S> | IComparableQuery<T, R, S> | any {
        return this.joinPropertyUsingAlias(propertySelector, this._lastAlias);
    }

    public thenJoinAlso<S extends Object>(
        propertySelector: (obj: P) => JoinedEntityType<S>
    ): IQuery<T, R, S> | IComparableQuery<T, R, S> | any {
        return this.joinPropertyUsingAlias(propertySelector, this._lastAlias, this._query.leftJoin);
    }

    public toPromise(): Promise<R> {
        return this._getAction.call(this.buildQuery(this));
    }

    public usingBaseType(): IQuery<T, R, T> {
        this._lastAlias = this._initialAlias;

        return <IQuery<T, R, T>><any>this;
    }

    public where<S extends Object, F = T | P>(
        propertySelector: (obj: F) => S
    ): IComparableQuery<T, R, T> | IComparableQuery<T, R, P> | any {
        const whereProperties: string = nameof<F>(propertySelector);
        let whereProperty: string = null;

        // Keep up with the last alias in order to restore it after joinMultipleProperties.
        let lastAlias: string = this._lastAlias;

        // In the event of performing a normal where after a join-based where, use the initial alias.
        if (this._queryMode === QueryMode.Get) {
            this._queryWhereType = QueryWhereType.Normal;
            // Following a normal where, restore last alias to the initial alias.
            // Last alias may be changed in joinMultipleProperties, but in case it is not,
            // it needs to be reset to the initial alias before setting the where property.
            lastAlias = this._lastAlias = this._initialAlias;

            // If accessing multiple properties, join relationships using an INNER JOIN.
            whereProperty = this.joinMultipleProperties(whereProperties);

            const where: string = `${this._lastAlias}.${whereProperty}`;
            this._queryParts.push(new QueryBuilderPart(
                this._query.where, [where]
            ));
        }
        // Otherwise, this where was performed on a join operation.
        else {
            // If accessing multiple properties, join relationships using an INNER JOIN.
            whereProperty = this.joinMultipleProperties(whereProperties);

            this._queryWhereType = QueryWhereType.Joined;
            this.createJoinCondition(whereProperty);
        }

        // Restore the last alias after joinMultipleProperties.
        this._lastAlias = lastAlias;

        this._queryMode = QueryMode.Compare;

        return <IComparableQuery<T, R, T>><any>this;
    }

    // TODO: Can we use something besides the VERY unfortunate "any" here?
    // With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
    // TS is complaining about IComparableQuery not having a whole bunch of methods
    // from IQuery (which is true).
    public whereAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector?: (obj: S) => ComparableValue
    ): any {
        return this.relationCount(
            relationSelector,
            relationCountPropSelector,
            this._query.having,
            ">",
            conditionPropSelector
        );
    }

    // TODO: Can we use something besides the VERY unfortunate "any" here?
    // With return type "IQuery<T, R, T> | IComparableQuery<T, R, S>",
    // TS is complaining about IComparableQuery not having a whole bunch of methods
    // from IQuery (which is true).
    public whereNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector?: (obj: S) => ComparableValue
    ): any {
        return this.relationCount(
            relationSelector,
            relationCountPropSelector,
            this._query.having,
            "=",
            conditionPropSelector
        );
    }

    private addJoinCondition(
        whereProperty: string, condition: "AND" | "OR",
        targetQueryPart: IQueryBuilderPart<T> = null
    ): void {
        // Result of calling .include(x => x.prop).where(...).<compare>(...).<and/or>(...)
        // [
        //     QueryBuilder.leftJoinAndSelect,
        //     ["alias.includedProperty", "includedProperty", "includedProperty.property = 'something'"]
        // ]
        // OR
        // Result of calling .join(x => x.pop).where(...).<compare>(...).<and/or>(...)
        // [
        //     QueryBuilder.innerJoin,
        //     ["alias.includedProperty", "includedProperty", "includedProperty.property = 'something'"]
        // ]
        const part: IQueryBuilderPart<T> = targetQueryPart || this._queryParts.pop();

        // "includedProperty.property = 'something'"
        let joinCondition: string = (<[string]>part.queryParams).pop();
        joinCondition += ` ${condition} ${this._lastAlias}.${whereProperty}`;
        (<[string]>part.queryParams).push(joinCondition);

        // If we did not receive the optional taretQueryPart argument,
        // that means we used the last query part, which was popped from this._queryParts.
        if (!targetQueryPart) {
            this._queryParts.push(part);
        }
    }

    private andOr<S extends Object, PP = P, RR = P>(
        propertySelector: (obj: PP) => S,
        operation: "AND" | "OR",
        queryAction: (where: string, parameters?: ObjectLiteral) => SelectQueryBuilder<T>
    ): IComparableQuery<T, R, RR> {
        const whereProperties: string = nameof<PP>(propertySelector);

        // If accessing multiple properties during an AND, join relationships using an INNER JOIN.
        // If accessing multiple properties during an OR, join relationships using a LEFT JOIN.
        const joinAction: (...params: any[]) => SelectQueryBuilder<T> =
            operation === "AND"
                ? this._query.innerJoin
                : this._query.leftJoin;

        // Keep up with the last alias in order to restore it after joinMultipleProperties.
        const lastAlias: string = this._lastAlias;

        // If accessing multiple properties, join relationships using an INNER JOIN.
        const whereProperty: string = this.joinMultipleProperties(whereProperties, joinAction);

        // A third parameter on the query parameters indicates additional join conditions.
        // Only add a join condition if performing a conditional join.
        if (
            this._queryWhereType === QueryWhereType.Joined &&
            this._queryParts[this._queryParts.length - 1].queryParams.length === 3
        ) {
            this.addJoinCondition(whereProperty, operation);
        }
        else {
            const where: string = `${this._lastAlias}.${whereProperty}`;
            this._queryParts.push(new QueryBuilderPart(
                queryAction,
                [where]
            ));
        }

        // Restore the last alias after joinMultipleProperties.
        this._lastAlias = lastAlias;

        this._queryMode = QueryMode.Compare;

        return <IComparableQuery<T, R, RR>><any>this;
    }

    private buildQuery(query: IQueryInternal<T, R, any>): SelectQueryBuilder<T> {
        // Unpack and apply the QueryBuilder parts.
        this.compileQueryParts(query.queryParts, query.query);

        return query.query;
    }

    private compileQueryParts<PT>(queryParts: IQueryBuilderPart<PT>[], builder: WhereExpression): void {
        if (queryParts.length) {
            for (const queryPart of queryParts) {
                queryPart.queryAction.call(builder, ...queryPart.queryParams);
            }
        }
    }

    private completeGroupBy(
        groupAction: (...params: any[]) => SelectQueryBuilder<T>,
        groupProperty: string
    ): IQuery<T, R, P> {
        this._queryParts.push(new QueryBuilderPart(
            groupAction,
            [groupProperty]
        ));

        return this;
    }

    private completeOrderBy(
        orderAction: (...params: any[]) => SelectQueryBuilder<T>,
        orderParams: [string, "ASC" | "DESC"],
        options?: QueryOrderOptions
    ): IQuery<T, R, P> {
        if (options) {
            if (typeof (options.nullsFirst) === "boolean") {
                orderParams.push(options.nullsFirst ? "NULLS FIRST" : "NULLS LAST");
            }
        }

        this._queryParts.push(new QueryBuilderPart(
            orderAction,
            orderParams
        ));

        return this;
    }

    private completeJoinedWhere(
        operator: string,
        selector: (obj: P) => any,
        options?: QueryConditionOptions
    ): IQuery<T, R, P> {
        const selectedProperty: string = nameof<P>(selector);
        const compareValue: string = `${this._lastAlias}.${selectedProperty}`;

        // compareValue is a string but should be treated as a join property
        // (not a quoted string) in the query, so use "false" for the "quoteString" argument.
        // If the user specifies a matchCase option, then assume the property is, in fact, a string
        // and allow completeWhere to apply case insensitivity if necessary.
        return this.completeWhere(
            operator,
            compareValue,
            {
                joiningString: !!options && typeof (options.matchCase) === "boolean",
                quoteString: false
            },
            options
        );
    }

    private completeWhere(
        operator: string,
        value: ComparableValue,
        optionsInternal?: QueryConditionOptionsInternal,
        options?: QueryConditionOptions
    ): IQuery<T, R, P> {
        let beginsWith: boolean = false;
        let endsWith: boolean = false;
        let joiningString: boolean = false;
        let quoteString: boolean = true;
        let matchCase: boolean = false;

        if (optionsInternal) {
            if (typeof (optionsInternal.beginsWith) === "boolean") {
                beginsWith = optionsInternal.beginsWith;
            }

            if (typeof (optionsInternal.endsWith) === "boolean") {
                endsWith = optionsInternal.endsWith;
            }

            if (typeof (optionsInternal.joiningString) === "boolean") {
                joiningString = optionsInternal.joiningString;
            }

            if (typeof (optionsInternal.quoteString) === "boolean") {
                quoteString = optionsInternal.quoteString;
            }
        }

        if (options) {
            if (typeof (options.matchCase) === "boolean") {
                matchCase = options.matchCase;
            }
        }

        if (beginsWith) {
            value += "%";
        }

        if (endsWith) {
            value = `%${value}`;
        }

        if (typeof (value) === "string" && quoteString) {
            value = value.replace(/'/g, "''");
            value = `'${value}'`;
        }

        // In case of a from or join within a "where", must find the last "where" in the query parts.
        const nonWheres: IQueryBuilderPart<T>[] = [];
        let wherePart: IQueryBuilderPart<T> = null;

        while (this._queryParts.length && !wherePart) {
            const part = this._queryParts.pop();
            if (
                (
                    // Could either be a normal where function:
                    this._queryWhereType === QueryWhereType.Normal
                    && (
                        // tslint:disable-next-line: triple-equals
                        part.queryAction == this._query.where
                        // tslint:disable-next-line: triple-equals
                        || part.queryAction == this._query.andWhere
                        // tslint:disable-next-line: triple-equals
                        || part.queryAction == this._query.orWhere
                    )
                )
                || (
                    // or a join condition:
                    this._queryWhereType === QueryWhereType.Joined
                    && (
                        part.queryAction === this._query.innerJoin
                        || part.queryAction === this._query.leftJoin
                        || part.queryAction === this._query.leftJoinAndSelect
                        || part.queryAction === this._query.innerJoinAndSelect
                    )
                    && part.queryParams.length === 3
                )
            ) {
                wherePart = part;
            }
            else {
                nonWheres.unshift(part);
            }
        }

        if (!wherePart) {
            throw new Error("typeorm-linq-repository: Invalid use of conditional method.");
        }

        this._queryParts.push(...nonWheres);

        // If processing a join condition.
        if (this._queryWhereType === QueryWhereType.Joined) {
            // [
            //     QueryBuilder.leftJoinAndSelect,
            //     ["alias.includedProperty", "includedProperty", "includedProperty.property"]
            // ]
            const part: IQueryBuilderPart<T> = wherePart;
            // "includedProperty.property"
            let joinCondition: string = (<[string]>part.queryParams).pop();

            if (typeof (value) === "string" && (quoteString || joiningString) && !matchCase) {
                value = value.toLowerCase();
                joinCondition = `LOWER(${joinCondition})`;
            }
            else if (value instanceof Date) {
                value = `'${value.toISOString()}'`;
            }

            // "includedProperty.property = 'something'"
            joinCondition += ` ${operator} ${value}`;
            (<[string]>part.queryParams).push(joinCondition);
            this._queryParts.push(part);
        }
        // If processing a normal comparison.
        else {
            // [QueryBuilder.<where | andWhere | orWhere>, ["alias.property"]]
            const part: IQueryBuilderPart<T> = wherePart;
            // "alias.property"
            let where: string = (<[string]>part.queryParams).pop();

            if (typeof (value) === "string" && (quoteString || joiningString) && !matchCase) {
                value = value.toLowerCase();
                where = `LOWER(${where})`;
            }
            else if (value instanceof Date) {
                value = `'${value.toISOString()}'`;
            }

            where += ` ${operator} ${value}`;
            (<[string, ObjectLiteral]>part.queryParams).push(where);
            this._queryParts.push(part);
        }

        this._queryMode = QueryMode.Get;

        return this;
    }

    private createJoinCondition(joinConditionProperty: string): void {
        // Find the query part on which to add the condition. Usually will be the last, but not always.
        let targetQueryPart: IQueryBuilderPart<T> = null;
        const otherParts: IQueryBuilderPart<T>[] = [];

        while (!targetQueryPart && this._queryParts.length) {
            const part: IQueryBuilderPart<T> = this._queryParts.pop();
            // See if this query part is the one in which the last alias was joined.
            if (part.queryParams && part.queryParams.length > 1 && part.queryParams[1] === this._lastAlias) {
                targetQueryPart = part;
            }
            else {
                otherParts.unshift(part);
            }
        }

        if (!targetQueryPart) {
            throw new Error("typeorm-linq-repository: Invalid use of conditional join.");
        }

        this._queryParts.push(...otherParts);

        // There should not already be a join condition on this query builder part.
        // If there is, we want to add a join condition, not overwrite it.
        if (targetQueryPart.queryParams.length === 3) {
            this.addJoinCondition(joinConditionProperty, "AND", targetQueryPart);
        }
        else {
            const joinCondition: string = `${this._lastAlias}.${joinConditionProperty}`;
            (<[string]>targetQueryPart.queryParams).push(joinCondition);
        }

        this._queryParts.push(targetQueryPart);
    }

    private escapeStringArray(array: string[]): void {
        array.forEach((value, i) => {
            if (typeof (value) === "string") {
                array[i] = `'${value}'`;
            }
        });
    }

    private includeOrExcludeFromInnerQuery<TI extends EntityBase, RI extends TI | TI[], PI1 = TI>(
        innerQuery: ISelectQueryInternal<TI, RI, PI1>,
        operator: string
    ): IQuery<T, R, P> {
        innerQuery.queryParts.unshift(new QueryBuilderPart(
            innerQuery.query.select, [innerQuery.selected]
        ));

        // Use <any> since all that matters is that the base type of any query
        // contains a property named "id".
        const query: string = this.buildQuery(<any>innerQuery)
            .getQuery();
        this.completeWhere(operator, `(${query})`, { quoteString: false });

        return this;
    }

    private includePropertyUsingAlias<S extends Object>(
        propertySelector: (obj: T | P) => JoinedEntityType<S>,
        queryAlias: string
    ): IQuery<T, R, S> {
        return this.joinOrIncludePropertyUsingAlias(
            propertySelector,
            queryAlias,
            this._query.leftJoinAndSelect
        );
    }

    private isolatedConditions<IP, IS>(
        conditions: (query: IQuery<T, R, P>) => IQuery<T, R, IS>,
        conditionAction: (...params: any[]) => SelectQueryBuilder<T>
    ): IQuery<T, R, IP> {
        const query: Query<T, R, IS> = <Query<T, R, IS>><any>conditions(<IQuery<T, R, P>><any>new Query(
            this._query,
            this._getAction,
            this._includeAliasHistory
        ));

        // Do not include joins in bracketed condition; perform those in the outer query.
        const conditionParts: IQueryBuilderPart<T>[] =
            query
                .queryParts
                .filter(qp =>
                    // tslint:disable-next-line: triple-equals
                    qp.queryAction == query.query.where
                    // tslint:disable-next-line: triple-equals
                    || qp.queryAction == query.query.andWhere
                    // tslint:disable-next-line: triple-equals
                    || qp.queryAction == query.query.orWhere
                );

        // Perform joins in the outer query.
        const joinParts: IQueryBuilderPart<T>[] =
            query
                .queryParts
                .filter(qp => conditionParts.indexOf(qp) < 0);
        this._queryParts.push(...joinParts);

        this._queryParts.push(new QueryBuilderPart(
            conditionAction,
            [
                new Brackets(qb => {
                    this.compileQueryParts(conditionParts, qb);
                })
            ]
        ));

        return <IQuery<T, R, IP>><any>this;
    }

    private joinForeignEntity<F extends Object>(
        foreignEntity: { new(...params: any[]): F; }
    ): IQuery<T, R, F> | IComparableQuery<T, R, F> {
        const entityName: string = nameof(foreignEntity);
        const resultAlias: string = entityName;
        this._lastAlias = resultAlias;
        // If just passing through a chain of possibly already executed includes for semantics,
        // don't execute the include again.
        // Only execute the include if it has not been previously executed.
        if (!(this._includeAliasHistory.find(a => a === resultAlias))) {
            this._includeAliasHistory.push(resultAlias);
            this._queryParts.push(new QueryBuilderPart(
                this._query.innerJoin, [foreignEntity, resultAlias, "true"]
            ));
        }

        this.setJoinIfNotCompare();

        return <IQuery<T, R, F> | IComparableQuery<T, R, F>><any>this;
    }

    private joinMultipleProperties(
        whereProperties: string,
        joinAction: (...params: any[]) => SelectQueryBuilder<T> = this._query.innerJoin,
        checkAliasHistory: boolean = true
    ): string {
        // Array.map() is used to select a property from a relationship collection.
        // .where(x => x.relationshipOne.map(y => y.relationshipTwo.map(z => z.relationshipThree)))...
        // Becomes, via ts-simple-nameof...
        // "relationshipOne.map(y => y.relationshipTwo.map(z => z.relationshipThree))"
        // Now get...
        // "relationshipOne.map(y=>y.relationshipTwo.map(z=>z.relationshipThree))"
        whereProperties = whereProperties.replace(/ /g, "");
        // "relationshipOne.relationshipTwo.relationshipThree"
        // Regex allows:
        // .map(y=>y.relationshipTwo)
        // or:
        // .map((y)=>y.relationshipTwo)
        // or:
        // .map((y:Entity)=>y.relationshipTwo)
        whereProperties = whereProperties
            .replace(/\.map\((\(?[a-zA-Z0-9_:]+\)?)=>[a-zA-Z0-9]+/g, "")
            .replace(/\)/g, "");

        const separatedProperties: string[] = whereProperties.split(".");

        // If not checking alias history, we are performing a <and/or/where><Any/None>,
        // so do not pop the last property, which is a relation rather than a primitive property.
        let whereProperty = "";

        if (checkAliasHistory) {
            whereProperty = separatedProperties.pop();
        }

        for (let property of separatedProperties) {
            // Array.map() is used to select a property from a relationship collection.
            if (property.indexOf("map(") === 0) {
                property = property.substring(4);
            }

            this.joinPropertyUsingAlias(property, this._lastAlias, joinAction, checkAliasHistory);
        }

        return whereProperty;
    }

    private joinOrIncludePropertyUsingAlias<S extends Object>(
        propertySelector: ((obj: T | P) => JoinedEntityType<S>) | string,
        queryAlias: string,
        queryAction: (...params: any[]) => SelectQueryBuilder<T>,
        checkAliasHistory: boolean = true
    ): IQuery<T, R, S> {
        let propertyName: string = null;

        if (propertySelector instanceof Function) {
            propertyName = nameof<P>(propertySelector);
        }
        else {
            propertyName = propertySelector;
        }

        let resultAlias: string = `${queryAlias}_${propertyName}`;

        // If including, do not set join mode.
        if (queryAction !== this._query.leftJoinAndSelect) {
            this.setJoinIfNotCompare();
        }

        // If not checking alias history, we are performing a <and/or/where><Any/None>,
        // so make this instance of this relation's alias unique.
        if (!checkAliasHistory) {
            const existingAliasCount = this._duplicateAliasHistory
                .filter(a => a === resultAlias)
                .length;

            this._duplicateAliasHistory.push(resultAlias);

            resultAlias += existingAliasCount.toString();
        }

        this._lastAlias = resultAlias;

        const aliasAlreadyIncluded = this._includeAliasHistory.some(a => a === resultAlias);

        // If just passing through a chain of possibly already executed includes for semantics,
        // don't execute the include again.
        // Only execute the include if it has not been previously executed OR if not checking alias history,
        // meaning we are performing an <and/or/where><Any/None>.
        if (!checkAliasHistory || !aliasAlreadyIncluded) {
            this._includeAliasHistory.push(resultAlias);
            const queryProperty: string = `${queryAlias}.${propertyName}`;
            this._queryParts.push(new QueryBuilderPart(
                queryAction,
                [queryProperty, resultAlias]
            ));
        }
        // If passing through a previous include but restricting the previously included entities
        // to a condition based on a deeper relationship, then restrict the previously used
        // leftJoinAndSelect to an innerJoinAndSelect instead.
        else if (
            checkAliasHistory
            && aliasAlreadyIncluded
            && queryAction === this._query.innerJoin
        ) {
            const includeQueryPartIndex = this._queryParts.findIndex(qp =>
                qp.queryAction === this._query.leftJoinAndSelect
                && qp.queryParams[1] === resultAlias
            );

            if (includeQueryPartIndex >= 0) {
                const includeQueryPart = this._queryParts[includeQueryPartIndex];
                this._queryParts[includeQueryPartIndex] = new QueryBuilderPart(
                    this._query.innerJoinAndSelect,
                    includeQueryPart.queryParams
                );
            }
        }

        return <IQuery<T, R, S>><any>this;
    }

    private joinPropertyUsingAlias<S extends Object>(
        propertySelector: ((obj: T | P) => JoinedEntityType<S>) | string,
        queryAlias: string,
        queryAction: (...params: any[]) => SelectQueryBuilder<T> = this._query.innerJoin,
        checkAliasHistory: boolean = true
    ): IQuery<T, R, S> {
        return this.joinOrIncludePropertyUsingAlias(propertySelector, queryAlias, queryAction, checkAliasHistory);
    }

    private relationCount<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        havingQueryAction: (where: string, parameters?: ObjectLiteral) => SelectQueryBuilder<T>,
        havingCountComparer: "=" | ">",
        conditionPropSelector?: (obj: S) => ComparableValue
    ): IQuery<T, R, T> | IComparableQuery<T, R, S> {
        // When using <and/or/where><Any/None>, always start at base type.
        this.reset();

        // Note: For simplicity, always LEFT JOIN the specified relationship and perform check on that instance of LEFT JOIN.
        // There may be potential for optimization of this in the future, but it will be tricky.
        const relations: string = nameof<T>(relationSelector);
        this.joinMultipleProperties(
            relations,
            this._query.leftJoin,
            false
        );

        // Add arbitrary COUNT property to HAVING statement driving this check.
        const countProp = nameof<S>(relationCountPropSelector);
        this._queryParts.push(new QueryBuilderPart(
            havingQueryAction,
            [`COUNT(${this._lastAlias}.${countProp}) ${havingCountComparer} 0`]
        ));

        // Create join condition if necessary.
        if (conditionPropSelector) {
            const conditionProp = nameof<S>(conditionPropSelector);
            // Set QueryWhereType.Joined to enable valid use of conditional method.
            this._queryWhereType = QueryWhereType.Joined;
            this.createJoinCondition(conditionProp);
        }

        return <IQuery<T, R, T> | IComparableQuery<T, R, S>><any>this;
    }

    private setJoinIfNotCompare(): void {
        // We may be joining a relation to make a comparison on that relation.
        // If so, leave QueryMode as Compare.
        // If not, set QueryMode to Join.
        if (this._queryMode !== QueryMode.Compare) {
            this._queryMode = QueryMode.Join;
        }
    }
}


================================================
FILE: src/query/QueryBuilderPart.ts
================================================
import { SelectQueryBuilder } from "typeorm";
import { EntityBase } from "../types/EntityBase";
import { IQueryBuilderPart } from "./interfaces/IQueryBuilderPart";

export class QueryBuilderPart<T extends EntityBase> implements IQueryBuilderPart<T> {
    private readonly _queryAction: (...params: any[]) => SelectQueryBuilder<T>;
    private readonly _queryParams: any[];

    public constructor(queryAction: (...params: any[]) => SelectQueryBuilder<T>, queryParams: any[]) {
        this._queryAction = queryAction;
        this._queryParams = queryParams;
    }

    public get queryAction(): (...params: any[]) => SelectQueryBuilder<T> {
        return this._queryAction;
    }

    public get queryParams(): any[] {
        return this._queryParams;
    }
}


================================================
FILE: src/query/interfaces/IComparableQuery.ts
================================================
import { ComparableValue } from "../../types/ComparableValue";
import { EntityBase } from "../../types/EntityBase";
import { JoinedEntityType } from "../../types/JoinedEntityType";
import { QueryConditionOptions } from "../../types/QueryConditionOptions";
import { IComparableQueryBase } from "./IComparableQueryBase";
import { IJoinedComparableQuery } from "./IJoinedComparableQuery";
import { IQuery } from "./IQuery";
import { ISelectQuery } from "./ISelectQuery";

/**
 * Finalizes the comparison portion of a Query operation or joins a relation or foreign entity against which to compare a value.
 */
export interface IComparableQuery<T extends EntityBase, R extends T | T[], P = T> extends IComparableQueryBase<T, R, P> {
    /**
     * Finds results where the specified property starts with the provided string (using LIKE "string%").
     * @param value The value against which to compare.
     * @param options Options for query conditions such as string case matching.
     */
    beginsWith(value: string, options?: QueryConditionOptions): IQuery<T, R, P>;
    /**
     * Finds results where the specified property contains the provided string (using LIKE "%string%").
     * @param value The value against which to compare.
     * @param options Options for query conditions such as string case matching.
     */
    contains(value: string, options?: QueryConditionOptions): IQuery<T, R, P>;
    /**
     * Finds results where the specified property ends with the provided string (using LIKE "%string").
     * @param value The value against which to compare.
     * @param options Options for query conditions such as string case matching.
     */
    endsWith(value: string, options?: QueryConditionOptions): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected property is equal to the specified value.
     * @param value The value against which to compare.
     * @param options Options for query conditions such as string case matching.
     */
    equal(value: ComparableValue, options?: QueryConditionOptions): IQuery<T, R, P>;
    /**
     * Joins an unrelated table using a TypeORM entity.
     * @type {F} The type of the foreign entity to join.
     * @param foreignEntity The TypeORM entity whose table to join.
     */
    from<F extends EntityBase>(foreignEntity: { new(...params: any[]): F; }): IJoinedComparableQuery<T, R, F>;
    /**
     * Determines whether the previously selected property is greater than the specified value.
     * @param value The value against which to compare.
     */
    greaterThan(value: number | Date): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected property is greater than or equal to the specified value.
     * @param value The value against which to compare.
     */
    greaterThanOrEqual(value: number | Date): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected value is contained in the specified array of values.
     * @param include The array of values to check for inclusion of the previously selected value.
     * @param options Options for query conditions such as string case matching.
     */
    in(include: string[] | number[], options?: QueryConditionOptions): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected value is contained in the result of values selected from an inner query.
     * @type {TI} The base type of the inner Query.
     * @type {RI} The return type of the inner Query.
     * @type {PI1} The type of the last joined navigation property from the inner Query.
     * @param innerQuery The inner query from which to select the specified value.
     */
    inSelected<TI extends EntityBase, RI extends TI | TI[], PI1 = TI>(innerQuery: ISelectQuery<TI, RI, PI1>): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected property is false.
     * @param value The value to check for falsity.
     */
    isFalse(): IQuery<T, R, P>;
    /**
     * Finds results where the specified property is not null.
     */
    isNotNull(): IQuery<T, R, P>;
    /**
     * Finds results where the specified property is null.
     */
    isNull(): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected property is true.
     * @param value The value to check for truth.
     */
    isTrue(): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected property is less than the specified value.
     * @param value The value against which to compare.
     */
    lessThan(value: number | Date): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected property is less than or equal to the specified value.
     * @param value The value against which to compare.
     */
    lessThanOrEqual(value: number | Date): IQuery<T, R, P>;
    /**
     * Joins the specified navigation property for where conditions on that property.
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to join, ex. x => x.prop
     */
    join<S extends Object>(propertySelector: (obj: T) => JoinedEntityType<S>): IJoinedComparableQuery<T, R, S>;
    /**
     * Determines whether the previously selected property differs from the specified value.
     * @param value The value against which to compare.
     * @param options Options for query conditions such as string case matching.
     */
    notEqual(value: string | number | boolean, options?: QueryConditionOptions): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected value is not contained in the specified array of values.
     * @param include The array of values to check for exclusion of the previously selected value.
     * @param options Options for query conditions such as string case matching.
     */
    notIn(exclude: string[] | number[], options?: QueryConditionOptions): IQuery<T, R, P>;
    /**
     * Determines whether the previously selected value is not contained in the result of values selected from an inner query.
     * @type {TI} The base type of the inner Query.
     * @type {RI} The return type of the inner Query.
     * @type {PI1} The type of the last joined navigation property from the inner Query.
     * @param innerQuery The inner query from which to select the specified property.
     */
    notInSelected<TI extends EntityBase, RI extends TI | TI[], PI1 = TI>(innerQuery: ISelectQuery<TI, RI, PI1>): IQuery<T, R, P>;
}

================================================
FILE: src/query/interfaces/IComparableQueryBase.ts
================================================
import { EntityBase } from "../../types/EntityBase";
import { JoinedEntityType } from "../../types/JoinedEntityType";
import { IJoinedComparableQuery } from "./IJoinedComparableQuery";

/**
 * Enables IComparableQuery and IJoinedComparableQuery to join a relation from the current Query type.
 */
export interface IComparableQueryBase<T extends EntityBase, R extends T | T[], P = T> {
    /**
     * Joins a subsequent navigation property on the previously joined relationship of type P for where conditions on that property.
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to join, ex. x => x.prop
     */
    thenJoin<S extends Object>(propertySelector: (obj: P) => JoinedEntityType<S>): IJoinedComparableQuery<T, R, S>;
}

================================================
FILE: src/query/interfaces/IJoinedComparableQuery.ts
================================================
import { EntityBase } from "../../types/EntityBase";
import { QueryConditionOptions } from "../../types/QueryConditionOptions";
import { IComparableQueryBase } from "./IComparableQueryBase";
import { IQuery } from "./IQuery";

/**
 * Finalizes the comparing portion of a Query operation by performing comparison with the specified joined value.
 */
export interface IJoinedComparableQuery<T extends EntityBase, R extends T | T[], P = T> extends IComparableQueryBase<T, R, P> {
    /**
     * Determines whether the property specified in the last "where" is equal to the specified property on the last joined entity.
     * @param selector Property selection lambda for property to compare, ex. x => x.prop
     * @param options Options for query conditions such as string case matching.
     */
    equalJoined(selector: (obj: P) => any, options?: QueryConditionOptions): IQuery<T, R, P>;
    /**
     * Determines whether the property specified in the last "where" is greater than the specified property on the last joined entity.
     * @param selector Property selection lambda for property to compare, ex. x => x.prop
     */
    greaterThanJoined(selector: (obj: P) => any): IQuery<T, R, P>;
    /**
     * Determines whether the property specified in the last "where" is greater than or equal to the specified property on the last joined entity.
     * @param selector Property selection lambda for property to compare, ex. x => x.prop
     */
    greaterThanOrEqualJoined(selector: (obj: P) => any): IQuery<T, R, P>;
    /**
     * Determines whether the property specified in the last "where" is less than the specified property on the last joined entity.
     * @param selector Property selection lambda for property to compare, ex. x => x.prop
     */
    lessThanJoined(selector: (obj: P) => any): IQuery<T, R, P>;
    /**
     * Determines whether the property specified in the last "where" is less than or equal to the specified property on the last joined entity.
     * @param selector Property selection lambda for property to compare, ex. x => x.prop
     */
    lessThanOrEqualJoined(selector: (obj: P) => any): IQuery<T, R, P>;
    /**
     * Determines whether the property specified in the last "where" is not equal to the specified property on the last joined entity.
     * @param selector Property selection lambda for property to compare, ex. x => x.prop
     * @param options Options for query conditions such as string case matching.
     */
    notEqualJoined(selector: (obj: P) => any, options?: QueryConditionOptions): IQuery<T, R, P>;
}

================================================
FILE: src/query/interfaces/IJoinedQuery.ts
================================================
import { EntityBase } from "../../types/EntityBase";
import { IComparableQuery } from "./IComparableQuery";
import { IQueryBase } from "./IQueryBase";
import { ISelectQuery } from "./ISelectQuery";

/**
 * Allows .where() to use the last joined entity's alias.
 */
export interface IJoinedQuery<T extends EntityBase, R extends T | T[], P = T> extends IQueryBase<T, R, P> {
    /**
     * Selects a property from the last joined entity to select while performing an inner query.
     * @param propertySelector Property selection lambda for the property to select.
     */
    select(propertySelector: (obj: P) => any): ISelectQuery<T, R, P>;
    /**
     * Filters the query with a conditional statement based on the last joined entity's type.
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to compare.
     */
    where<S extends Object>(propertySelector: (obj: P) => S): IComparableQuery<T, R, P>;
}

================================================
FILE: src/query/interfaces/IQuery.ts
================================================
import { EntityBase } from "../../types/EntityBase";
import { IComparableQuery } from "./IComparableQuery";
import { IQueryBase } from "./IQueryBase";
import { ISelectQuery } from "./ISelectQuery";
import { JoinedEntityType } from "../../types/JoinedEntityType";
import { ComparableValue } from "../../types/ComparableValue";

/**
 * Basic query operations for Queries that are not in Comparable mode.
 */
export interface IQuery<T extends EntityBase, R extends T | T[], P = T> extends IQueryBase<T, R, P> {
    /**
     * Checks whether any items exist in the specified relationship (used after whereNone or whereAny).
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     */
    andAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue
    ): IQuery<T, R, T>;
    /**
     * Checks whether any items that meet certain criteria exist in the specified relationship (used after whereNone or whereAny).
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     * @param conditionPropSelector Property on which to compare
     */
    andAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector: (obj: S) => ComparableValue
    ): IComparableQuery<T, R, S>;
    /**
     * Checks whether no items exist in the specified relationship (used after whereNone or whereAny).
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     */
    andNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue
    ): IQuery<T, R, T>;
    /**
     * Checks whether no items that meet certain criteria exist in the specified relationship (used after whereNone or whereAny).
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     * @param conditionPropSelector Property on which to compare.
     */
    andNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector: (obj: S) => ComparableValue
    ): IComparableQuery<T, R, S>;
    /**
     * Isolates a group of conditions into one WHERE clause.
     * @param where The Query representing the WHERE conditions to group.
     */
    isolatedWhere<S extends Object>(where: (query: IQuery<T, R, T>) => IQuery<T, R, S>): IQuery<T, R, T>;
    /**
     * Checks whether any items exist in the specified relationship (used after whereNone or whereAny).
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     */
    orAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue
    ): IQuery<T, R, T>;
    /**
     * Checks whether any items that meet certain criteria exist in the specified relationship (used after whereNone or whereAny).
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     * @param conditionPropSelector Property on which to compare.
     */
    orAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector: (obj: S) => ComparableValue
    ): IComparableQuery<T, R, S>;
    /**
     * Checks whether no items exist in the specified relationship (used after whereNone or whereAny).
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     */
    orNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue
    ): IQuery<T, R, T>;
    /**
     * Checks whether no items that meet certain criteria exist in the specified relationship (used after whereNone or whereAny).
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     * @param conditionPropSelector Property on which to compare
     */
    orNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector: (obj: S) => ComparableValue
    ): IComparableQuery<T, R, S>;
    /**
     * Selects a property from the last joined entity to select while performing an inner query.
     * @param propertySelector Property selection lambda for the property to select.
     */
    select(propertySelector: (obj: T) => any): ISelectQuery<T, R, T>;
    /**
     * Filters the query with a conditional statement based on the query's base type.
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to compare.
     */
    where<S extends Object>(propertySelector: (obj: T) => S): IComparableQuery<T, R, T>;
    /**
     * Checks whether any items exist in the specified relationship.
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     */
    whereAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue
    ): IQuery<T, R, T>;
    /**
     * Checks whether any items that meet certain criteria exist in the specified relationship.
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     * @param conditionPropSelector Property on which to compare.
     */
    whereAny<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector: (obj: S) => ComparableValue
    ): IComparableQuery<T, R, S>;
    /**
     * Checks whether no items exist in the specified relationship.
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     */
    whereNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue
    ): IQuery<T, R, T>;
    /**
     * Checks whether no items that meet certain criteria exist in the specified relationship.
     * NOTE: This method requires an arbitrary groupBy call (for instance, the base entity's primary key).
     * @param relationSelector The relationship to check for items.
     * @param relationCountPropSelector Arbitrary primitive property on which to count relation items.
     * @param conditionPropSelector Property on which to compare
     */
    whereNone<S extends Object>(
        relationSelector: (obj: T) => JoinedEntityType<S>,
        relationCountPropSelector: (obj: S) => ComparableValue,
        conditionPropSelector: (obj: S) => ComparableValue
    ): IComparableQuery<T, R, S>;
}

================================================
FILE: src/query/interfaces/IQueryBase.ts
================================================
import { EntityBase } from "../../types/EntityBase";
import { JoinedEntityType } from "../../types/JoinedEntityType";
import { QueryOrderOptions } from "../../types/QueryOrderOptions";
import { IComparableQuery } from "./IComparableQuery";
import { IJoinedQuery } from "./IJoinedQuery";
import { IQuery } from "./IQuery";

/**
 * Base set of operations for all Queries that are not in Comparable mode.
 */
export interface IQueryBase<T extends EntityBase, R extends T | T[], P = T> {
    /**
     * Adds an additional logical AND condition for which to query results.
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to compare.
     */
    and<S extends Object>(propertySelector: (obj: P) => S): IComparableQuery<T, R, P>;
    /**
     * Catches an error thrown during the execution of the underlying QueryBuilder's Promise.
     * @param rejected The rejection callback for the error thrown on the underlying QueryBuilder's Promise.
     */
    catch(rejected: (error: any) => void | Promise<any> | IQuery<any, any>): Promise<any>;
    /**
     * Gets the count of results matching the current query conditions.
     */
    count(): Promise<number>;
    /**
     * Joins an unrelated table using a TypeORM entity.
     * @type {F} The type of the foreign entity to join.
     * @param foreignEntity The TypeORM entity whose table to join.
     */
    from<F extends EntityBase>(foreignEntity: { new(...params: any[]): F; }): IJoinedQuery<T, R, F>;
    /**
     * Groups entities on the specified property.
     * @param propertySelector Property selection lambda for property by which to group.
     */
    groupBy(propertySelector: (obj: P) => any): IQuery<T, R, P>;
    /**
     * Includes the specified navigation property in the queried results.
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to include, ex. x => x.prop
     */
    include<S>(propertySelector: (obj: T) => JoinedEntityType<S>): IQuery<T, R, S>;
    /**
     * Isolates a group of conditions into one AND clause.
     * @param and The Query representing the AND conditions to group.
     */
    isolatedAnd<S extends Object>(and: (query: IQuery<T, R, P>) => IQuery<T, R, S>): IQuery<T, R, P>;
    /**
     * Isolates a group of conditions into one OR clause.
     * @param or The Query representing the OR conditions to group.
     */
    isolatedOr<S extends Object>(or: (query: IQuery<T, R, P>) => IQuery<T, R, S>): IQuery<T, R, P>;
    /**
     * Joins the specified navigation property using an INNER JOIN
     * (thus excluding results from the joining entity if its joined relationship fails the next join condition)
     * without including it in the results (useful for subsequent join conditions).
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to join, ex. x => x.prop
     */
    join<S extends Object>(propertySelector: (obj: T) => JoinedEntityType<S>): IJoinedQuery<T, R, S>;
    /**
     * Joins the specified navigation property using a LEFT JOIN
     * (thus including results from the joining entity even if its joined relationship fails the next join condition)
     * without including it in the results (useful for subsequent join conditions).
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to join, ex. x => x.prop
     */
    joinAlso<S extends Object>(propertySelector: (obj: T) => JoinedEntityType<S>): IJoinedQuery<T, R, S>;
    /**
     * Adds an additional logical OR condition for which to query results.
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to compare.
     */
    or<S extends Object>(propertySelector: (obj: P) => S): IComparableQuery<T, R, P>;
    /**
     * Orders the query on the specified property in ascending order.
     * @param propertySelector Property selection lambda for property on which to sort.
     */
    orderBy(propertySelector: (obj: P) => any, options?: QueryOrderOptions): IQuery<T, R, P>;
    /**
     * Orders the query on the specified property in descending order.
     * @param propertySelector Property selection lambda for property on which to sort.
     */
    orderByDescending(propertySelector: (obj: P) => any, options?: QueryOrderOptions): IQuery<T, R, P>;
    /**
     * Returns the query back to its base type while also exiting "join mode",
     * thus ending a join chain so that where conditions may be continued on the base type.
     */
    reset(): IQuery<T, R, T>;
    /**
     * Sets the number of results to skip before taking results from the query.
     * @param skip The number of results to skip.
     */
    skip(skip: number): IQuery<T, R, P>;
    /**
     * Limits the number of results to take from the query.
     * @param limit The number of results to take.
     */
    take(limit: number): IQuery<T, R, P>;
    /**
     * Executes the query by invoking the Promise to get the underlying QueryBuilder's results.
     * @param resolved The resolution callback for the underlying QueryBuilder's results Promise.
     */
    then(resolved: (results: R) => void | Promise<any>): Promise<any>;
    /**
     * Adds a subsequent ordering to the query on the specified property in ascending order.
     * @param propertySelector Property selection lambda for property on which to sort.
     */
    thenBy(propertySelector: (obj: P) => any, options?: QueryOrderOptions): IQuery<T, R, P>;
    /**
     * Adds a subsequent ordering to the query on the specified property in descending order.
     * @param propertySelector Property selection lambda for property on which to sort.
     */
    thenByDescending(propertySelector: (obj: P) => any, options?: QueryOrderOptions): IQuery<T, R, P>;
    /**
     * Groups entities on a subsequent specified property.
     * @param propertySelector Property selection lambda for property by which to subsequently group.
     */
    thenGroupBy(propertySelector: (obj: P) => any): IQuery<T, R, P>;
    /**
     * Includes a subsequent navigation property in the previously included relationship of type P.
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to include, ex. x => x.prop
     */
    thenInclude<S extends Object>(propertySelector: (obj: P) => JoinedEntityType<S>): IQuery<T, R, S>;
    /**
     * Joins a subsequent navigation property on the previously joined relationship of type P
     * (thus excluding results from the joining entity if its joined relationship fails the next join condition)
     * without including it in the results (useful for subsequent join conditions).
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to join, ex. x => x.prop
     */
    thenJoin<S extends Object>(propertySelector: (obj: P) => JoinedEntityType<S>): IJoinedQuery<T, R, S>;
    /**
     * Joins a subsequent navigation property on the previously joined relationship of type P using a LEFT JOIN
     * (thus including results from the joining entity even if its joined relationship fails the next join condition)
     * without including it in the results (useful for subsequent join conditions).
     * @type {S} The type of the joined navigation property.
     * @param propertySelector Property selection lambda for property to join, ex. x => x.prop
     */
    thenJoinAlso<S extends Object>(propertySelector: (obj: P) => JoinedEntityType<S>): IJoinedQuery<T, R, S>;
    /**
     * Invokes and returns the Promise to get the underlying QueryBuilder's results.
     */
    toPromise(): Promise<R>;
    /**
     * Returns the "current query type" to the base type WITHOUT resetting join chains.
     * Therefore, does NOT allow where conditions to be continued on the base type,
     * but rather uses the base type in the current join chain.
     * @deprecated WARNING: This method was found to be faulty based on its initial intended use,
     * but remains nonetheless in case its use is, in fact, desired.
     * However, you may be looking for **reset()** instead.
     *
     * This method will remain deprecated for a while to alert users who used it based on
     * the initially intended use that it may result in unexpected behavior.
     * The deprecated status will be removed later so that users using it
     * based on the results it actually produces are not annoyed by it.
     */
    usingBaseType(): IQuery<T, R, T>;
}


================================================
FILE: src/query/interfaces/IQueryBuilderPart.ts
================================================
import { SelectQueryBuilder } from "typeorm";
import { EntityBase } from "../../types/EntityBase";

/**
 * Represents a part of a TypeORM SelectQueryBuilder of type T.
 */
export interface IQueryBuilderPart<T extends EntityBase> {
    queryParams: any[];
    queryAction(...params: any[]): SelectQueryBuilder<T>;
}


================================================
FILE: src/query/interfaces/IQueryInternal.ts
================================================
import { SelectQueryBuilder } from "typeorm";
import { EntityBase } from "../../types/EntityBase";
import { IQuery } from "./IQuery";
import { IQueryBuilderPart } from "./IQueryBuilderPart";

/**
 * Contains properties used internally by the Query class to construct TypeORM QueryBuilder queries from Queries.
 */
export interface IQueryInternal<T extends EntityBase, R extends T | T[], P = T> extends IQuery<T, R, P> {
    /**
     * Gets the underlying SelectQueryBuilder represented by the Query. Normally only used internally by the Query class for innery Queries.
     */
    query: SelectQueryBuilder<T>;
    /**
     * Gets the QueryParts used by the Query for the SelectQueryBuilder. Normally only used internally by the Query class for innery Queries.
     */
    queryParts: IQueryBuilderPart<T>[];
    /**
     * Gets the underlying database action used by the Query's SelectQueryBuilder. Normally only used internally by the Query class for innery Queries.
     */
    getAction(): Promise<R>;
}


================================================
FILE: src/query/interfaces/ISelectQuery.ts
================================================
import { EntityBase } from "../../types/EntityBase";

/**
 * Used to select the desired propery from the desired entity when using an inner query.
 * No further methods may be used after selecting a property because this interface is meant to serve just that purpose.
 */
export interface ISelectQuery<T extends EntityBase, R extends T | T[], P = T> {
}

================================================
FILE: src/query/interfaces/ISelectQueryInternal.ts
================================================
import { EntityBase } from "../../types/EntityBase";
import { IQueryInternal } from "./IQueryInternal";
import { ISelectQuery } from "./ISelectQuery";

export interface ISelectQueryInternal<T extends EntityBase, R extends T | T[], P = T> extends ISelectQuery<T, R, P>, IQueryInternal<T, R, P> {
    /**
     * Gets the propert that was selected from a Query to produce an ISelectQuery. Normally only used internally by the Query class for inner Queries.
     */
    selected: string;
}

================================================
FILE: src/repository/LinqRepository.ts
================================================
import { nameof } from "ts-simple-nameof";
import { DataSource, EntitySchema, Repository, SelectQueryBuilder } from "typeorm";
import { IQuery } from "../query/interfaces/IQuery";
import { Query } from "../query/Query";
import { EntityBase } from "../types/EntityBase";
import { EntityConstructor } from "../types/EntityConstructor";
import { RepositoryOptions } from "../types/RepositoryOptions";
import { ILinqRepository } from "./interfaces/ILinqRepository";

/**
 * Base repository operations for TypeORM entities.
 */
export class LinqRepository<T extends EntityBase> implements ILinqRepository<T> {
    protected readonly _repository: Repository<T>;

    private readonly _autoGenerateId: boolean;
    private readonly _primaryKeyName: string;

    /**
     * Constructs the repository for the specified entity with, unless otherwise specified,
     * a primry key named "id" that is auto-generated.
     * @param entityType The entity whose repository to create.
     * @param options Options for setting up the repository.
     */
    public constructor(
        dataSource: DataSource,
        entityType: EntityConstructor<T> | EntitySchema<T>,
        options?: RepositoryOptions<T>
    ) {
        let autoGenerateId: boolean = true;
        let primaryKeyName: string = "id";

        if (options) {
            if (typeof (options.autoGenerateId) === "boolean") {
                autoGenerateId = options.autoGenerateId;
            }

            if (options.primaryKey) {
                primaryKeyName = nameof(options.primaryKey);
            }
        }

        this._repository = dataSource.getRepository<T>(entityType);
        this._autoGenerateId = autoGenerateId;
        this._primaryKeyName = primaryKeyName;
    }

    public get typeormRepository(): Repository<T> {
        return this._repository;
    }

    public async create<E extends T | T[]>(entities: E): Promise<E> {
        if (this._autoGenerateId) {
            // Set "id" to undefined in order to allow auto-generation.
            if (entities instanceof Array) {
                for (const entity of (<T[]>entities)) {
                    // Not sure what is going on with this...
                    // Even defining EntityBase as { [key: string]: any; }
                    // or even Record<string, any> results in the error
                    // "Type 'string' cannot be used to index type T".
                    // https://github.com/microsoft/TypeScript/issues/31661
                    (<Record<string, any>>entity)[this._primaryKeyName] = undefined;
                }
            }
            else {
                // Not sure what is going on with this...
                // Even defining EntityBase as { [key: string]: any; }
                // or even Record<string, any> results in the error
                // "Type 'string' cannot be used to index type T".
                // https://github.com/microsoft/TypeScript/issues/31661
                (<Record<string, any>>entities)[this._primaryKeyName] = undefined;
            }
        }

        return this.upsert(entities);
    }

    public createQueryBuilder(alias: string): SelectQueryBuilder<T> {
        return this._repository.createQueryBuilder(alias);
    }

    public async delete(entities: number | string | T | T[]): Promise<boolean> {
        if (typeof (entities) === "number" || typeof (entities) === "string") {
            await this._repository.delete(entities);
        }
        else {
            await this._repository.remove(<any>entities);
        }

        return true;
    }

    public getAll(): IQuery<T, T[]> {
        const queryBuilder: SelectQueryBuilder<T> = this.createQueryBuilder("entity");
        const query: IQuery<T, T[]> = new Query(
            queryBuilder, queryBuilder.getMany
        );

        return query;
    }

    public getById(id: number | string): IQuery<T, T> {
        const alias: string = "entity";
        let queryBuilder: SelectQueryBuilder<T> = this.createQueryBuilder(alias);
        queryBuilder = queryBuilder.where(`${alias}.${this._primaryKeyName} = :id`, { id });
        const query: IQuery<T, T> = new Query(
            queryBuilder,
            queryBuilder.getOne
        );

        return query;
    }

    public getOne(): IQuery<T, T> {
        const queryBuilder: SelectQueryBuilder<T> = this.createQueryBuilder("entity");
        const query: IQuery<T, T> = new Query(
            queryBuilder,
            queryBuilder.getOne
        );

        return query;
    }

    public async update<E extends T | T[]>(entities: E): Promise<E> {
        return this.upsert(entities);
    }

    public async upsert<E extends T | T[]>(entities: E): Promise<E> {
        return <Promise<E>>this._repository.save(<any>entities);
    }
}


================================================
FILE: src/repository/interfaces/ILinqRepository.ts
================================================
import { Repository, SelectQueryBuilder } from "typeorm";
import { IQuery } from "../../query/interfaces/IQuery";
import { EntityBase } from "../../types/EntityBase";

/**
 * Base repository operations for TypeORM entities.
 */
export interface ILinqRepository<T extends EntityBase> {
    /**
     * The underlying TypeORM repository.
     */
    typeormRepository: Repository<T>;
    /**
     * Creates one or more entities in the database.
     * @param entities The entity or entities to create.
     */
    create<E extends T | T[]>(entities: E): Promise<E>;
    /**
     * Gets an instance of a QueryBuilder (useful if the Query returned by this repository does not meet your needs yet).
     */
    createQueryBuilder(alias: string): SelectQueryBuilder<T>;
    /**
     * Deletes one or more entities from the database.
     * @param entities The entity or entities to delete or the ID of the entity to delete.
     */
    delete(entities: number | string | T | T[]): Promise<boolean>;
    /**
     * Returns a Query returning a set of results.
     */
    getAll(): IQuery<T, T[]>;
    /**
     * Finds one entity with the specified ID.
     * @param id The ID of the entity to find.
     */
    getById(id: number | string): IQuery<T, T>;
    /**
     * Returns a Query returning one entity.
     */
    getOne(): IQuery<T, T>;
    /**
     * Upserts one or more entities in the database.
     * Note: Now an alias for upsert.
     * @param entities The entity or entities to upsert.
     */
    update<E extends T | T[]>(entities: E): Promise<E>;
    /**
     * Upserts one or more entities in the database.
     * @param entities The entities or entities to upsert.
     */
    upsert<E extends T | T[]>(entities: E): Promise<E>;
}

================================================
FILE: src/types/ComparableValue.ts
================================================
export type ComparableValue = string | number | boolean | Date;


================================================
FILE: src/types/EntityBase.ts
================================================
export declare type EntityBase = {};


================================================
FILE: src/types/EntityConstructor.ts
================================================
import { EntityBase } from "./EntityBase";

export declare type EntityConstructor<T extends EntityBase> = { new (...params: any[]): T; };


================================================
FILE: src/types/JoinedEntityType.ts
================================================
// Support *-to-one, one-to-many, and lazy loaded relations.
export declare type JoinedEntityType<J> = J | J[] | Promise<J[]> | Promise<J>;


================================================
FILE: src/types/QueryConditionOptions.ts
================================================
/**
 * Options for query conditions such as string case matching.
 */
export interface QueryConditionOptions {
    /**
     * Whether to enforce case sensitivity when comparing strings.
     */
    matchCase?: boolean;
}

================================================
FILE: src/types/QueryConditionOptionsInternal.ts
================================================
export interface QueryConditionOptionsInternal {
    beginsWith?: boolean;
    endsWith?: boolean;
    quoteString?: boolean;
    joiningString?: boolean;
}

================================================
FILE: src/types/QueryOrderOptions.ts
================================================
/**
 * Options for query ordering such as where to put nulls (supported by some databases).
 */
export interface QueryOrderOptions {
    /**
     * Whether to put null values first when ordering query results.
     */
    nullsFirst?: boolean;
}

================================================
FILE: src/types/RepositoryOptions.ts
================================================
import { EntityBase } from "./EntityBase";

/**
 * Options for setting up the repository.
 */
export interface RepositoryOptions<T extends EntityBase> {
    /**
     * True if the entity contains a primary key that is auto-generated; defaults to true.
     * If the auto-generated primary key is NOT "id", then also set "primaryKeyName".
     */
    autoGenerateId?: boolean;
    /**
     * The entity's primary key property in lambda form (e.g. "e => e.entityId");
     * if omitted, the default primary key property name is "id".
     */
    primaryKey?(entity: T): string | number;
}


================================================
FILE: test/entities/artist.entity.ts
================================================
import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
import { Song } from "./song.entity";

@Entity()
export class Artist {
    @PrimaryColumn()
    public id: number;

    @Column({ length: 100, nullable: false })
    public name: string;

    @OneToMany(() => Song, a => a.artist)
    public songs?: Song[];
}


================================================
FILE: test/entities/genre.entity.ts
================================================
import { Column, Entity, PrimaryColumn } from "typeorm";

@Entity()
export class Genre {
    @PrimaryColumn()
    public id: number;

    @Column({ length: 50, nullable: false })
    public name: string;
}


================================================
FILE: test/entities/song.entity.ts
================================================
import { nameof } from "ts-simple-nameof";
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
import { Artist } from "./artist.entity";
import { Genre } from "./genre.entity";

@Entity()
export class Song {
    @ManyToOne(() => Artist, a => a.songs)
    @JoinColumn({ name: nameof<Song>(s => s.artistId) })
    public artist?: Artist;

    @Column({ nullable: false })
    public artistId: number;

    @ManyToOne(() => Genre)
    @JoinColumn({ name: nameof<Song>(s => s.genreId) })
    public genre?: Genre;

    @Column({ nullable: false })
    public genreId: number;

    @PrimaryColumn()
    public id: number;

    @Column({ length: 100, nullable: false })
    public name: string;
}


================================================
FILE: test/entities/user-profile-attribute.entity.ts
================================================
import { nameof } from "ts-simple-nameof";
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from "typeorm";
import { Genre } from "./genre.entity";
import { User } from "./user.entity";

@Entity()
export class UserProfileAttribute {
    @ManyToOne(() => Genre)
    @JoinColumn({ name: nameof<UserProfileAttribute>(upa => upa.genreId) })
    public genre?: Genre;

    @Column({ nullable: false })
    public genreId: number;

    @PrimaryColumn()
    public id: number;

    @ManyToOne(() => User, u => u.profileAttributes)
    @JoinColumn({ name: nameof<UserProfileAttribute>(upa => upa.userId) })
    public user?: User;

    @Column({ nullable: false })
    public userId: number;
}


================================================
FILE: test/entities/user.entity.ts
================================================
import { Entity, OneToMany, PrimaryColumn } from "typeorm";
import { UserProfileAttribute } from "./user-profile-attribute.entity";

@Entity()
export class User {
    @PrimaryColumn()
    public id: number;

    @OneToMany(() => UserProfileAttribute, upa => upa.user)
    public profileAttributes?: UserProfileAttribute[];
}


================================================
FILE: test/jasmine-ts.helper.js
================================================
const { register } = require("ts-node");

register({
    project: "tsconfig.spec.json"
});


================================================
FILE: test/jasmine.json
================================================
{
    "spec_files": [
        "test/scenarios/**/*.spec.ts"
    ],
    "helpers": [
        "test/jasmine-ts.helper.js"
    ],
    "stopSpecOnExpectationFailure": false,
    "random": false
}


================================================
FILE: test/scenarios/query/query.spec.ts
================================================
import { DataSource } from "typeorm";
import { getTypeormDataSource } from "../../../.typeorm/connection/get-typeorm-data-source.function";
import { LinqRepository } from "../../../src/repository/LinqRepository";
import { Artist } from "../../entities/artist.entity";
import { Song } from "../../entities/song.entity";
import { UserProfileAttribute } from "../../entities/user-profile-attribute.entity";

describe("Query", () => {
    let dataSource: DataSource;

    let artistRepository: LinqRepository<Artist>;
    let songRepository: LinqRepository<Song>;

    beforeAll(async () => {
        dataSource = await getTypeormDataSource();

        artistRepository = new LinqRepository(dataSource, Artist);
        songRepository = new LinqRepository(dataSource, Song);
    });

    it("gets all entities", async () => {
        const artists = await artistRepository.getAll();

        expect(artists.length)
            .toBe(3);
    });

    it("gets many entities", async () => {
        const rockSongs = await songRepository
            .getAll()
            .where(s => s.genreId)
            .equal(1);

        expect(rockSongs.length)
            .toBe(3);
    });

    it("gets one entity", async () => {
        const song = await songRepository
            .getOne()
            .where(s => s.artistId)
            .equal(1)
            .and(s => s.genreId)
            .equal(3);

        expect(song)
            .not
            .toBeUndefined();
    });

    it("gets entity by id", async () => {
        const song = await songRepository.getById(1);

        expect(song)
            .not
            .toBeUndefined();
    });

    it("counts entities", async () => {
        const rockSongCount = await songRepository
            .getAll()
            .where(s => s.genreId)
            .equal(1)
            .count();

        expect(rockSongCount)
            .toBe(3);
    });

    it("includes entities", async () => {
        const song = await songRepository
            .getById(1)
            .include(s => s.artist)
            .include(s => s.genre);

        expect(song.artist)
            .not
            .toBeUndefined();
        expect(song.genre)
            .not
            .toBeUndefined();
    });

    it("isolates and conditions", async () => {
        const songs = await songRepository
            .getAll()
            .where(s => s.artistId)
            .equal(1)
            .isolatedAnd(q => q
                .where(s => s.genreId)
                .equal(1)
                .or(s => s.genreId)
                .equal(2)
            );

        expect(songs.length)
            .toBe(2);
    });

    it("isolates or conditions", async () => {
        const songs = await songRepository
            .getAll()
            .where(s => s.artistId)
            .equal(1)
            .isolatedOr(q => q
                .where(s => s.genreId)
                .notEqual(2)
                .and(s => s.genreId)
                .notEqual(3)
            );

        expect(songs.length)
            .toBe(5);
    });

    it("joins mapped relations (without parens)", async () => {
        const artists = await artistRepository
            .getAll()
            .where(a => a.songs.map(s => s.genreId))
            .equal(1);

        expect(artists.length)
            .toBe(3);
    });

    it("joins mapped relations (with parens)", async () => {
        const artists = await artistRepository
            .getAll()
            // tslint:disable-next-line: arrow-parens
            .where((a) => a.songs.map((s) => s.genreId))
            .equal(1);

        expect(artists.length)
            .toBe(3);
    });

    it("filters where any (without condition)", async () => {
        const artists = await artistRepository
            .getAll()
            .whereAny(a => a.songs, s => s.id)
            // Must add all columns to group by to mitigate
            // "incompatible with sql_mode=only_full_group_by" errors in mysql...
            .reset()
            .groupBy(a => a.id)
            .thenGroupBy(a => a.name);

        expect(artists.length)
            .toBe(3);
    });

    it("filters where any (with condition)", async () => {
        const artists = await artistRepository
            .getAll()
            .whereAny(a => a.songs, s => s.id, s => s.genreId)
            .equal(2)
            // Must add all columns to group by to mitigate
            // "incompatible with sql_mode=only_full_group_by" errors in mysql...
            .reset()
            .groupBy(a => a.id)
            .thenGroupBy(a => a.name);

        expect(artists.length)
            .toBe(2);
    });

    it("filters where none (without condition)", async () => {
        const artists = await artistRepository
            .getAll()
            .whereNone(a => a.songs, s => s.id)
            // Must add all columns to group by to mitigate
            // "incompatible with sql_mode=only_full_group_by" errors in mysql...
            .reset()
            .groupBy(a => a.id)
            .thenGroupBy(a => a.name);

        expect(artists.length)
            .toBe(0);
    });

    it("filters where none (with condition)", async () => {
        const artists = await artistRepository
            .getAll()
            .whereNone(a => a.songs, s => s.id, s => s.genreId)
            .equal(2)
            // Must add all columns to group by to mitigate
            // "incompatible with sql_mode=only_full_group_by" errors in mysql...
            .reset()
            .groupBy(a => a.id)
            .thenGroupBy(a => a.name);

        expect(artists.length)
            .toBe(1);
    });

    it("works with inner queries", async () => {
        const songs = await songRepository
            .getAll()
            .join(s => s.artist)
            .where(a => a.id)
            .equal(1)
            .where(s => s.id)
            .notInSelected(
                songRepository
                    .getAll()
                    .join(s => s.artist)
                    .where(a => a.id)
                    .equal(1)
                    .from(UserProfileAttribute)
                    .thenJoin(upa => upa.genre)
                    .where(g => g.id)
                    .join(s => s.genre)
                    .equalJoined(g => g.id)
                    .from(UserProfileAttribute)
                    .thenJoin(upa => upa.user)
                    .where(u => u.id)
                    .equal(1)
                    .select(s => s.id)
            );

        expect(songs.length)
            .toBe(2);
    });

    afterAll(async () => {
        await dataSource.destroy();
    });
});


================================================
FILE: tsconfig.build.json
================================================
{
    "extends": "./tsconfig.json",
    "exclude": [
        ".typeorm",
        "test"
    ]
}

================================================
FILE: tsconfig.json
================================================
{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es6",
        "lib": [
            "es6"
        ],
        "resolveJsonModule": true,
        "noImplicitAny": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "rootDir": "."
    }
}

================================================
FILE: tsconfig.spec.json
================================================
{
    "extends": "./tsconfig.json",
    "compilerOptions": {
        "sourceMap": true,
        "inlineSourceMap": true,
        "inlineSources": true
    }
}

================================================
FILE: tslint.json
================================================
{
    "rules": {
        "adjacent-overload-signatures": true,
        "align": true,
        "array-type": [
            true,
            "array"
        ],
        "arrow-parens": [
            true,
            "ban-single-arg-parens"
        ],
        "binary-expression-operand-order": true,
        "class-name": true,
        "comment-format": [
            true,
            "check-space"
        ],
        "curly": true,
        "forin": true,
        "import-spacing": true,
        "member-access": [
            true,
            "check-constructor"
        ],
        "member-ordering": [
            true,
            {
                "order": "instance-sandwich"
            }
        ],
        "new-parens": true,
        "newline-before-return": true,
        "newline-per-chained-call": true,
        "no-boolean-literal-compare": true,
        "no-consecutive-blank-lines": true,
        "no-construct": true,
        "no-duplicate-imports": true,
        "no-duplicate-super": true,
        "no-duplicate-switch-case": true,
        "no-duplicate-variable": true,
        "no-for-in-array": true,
        "no-internal-module": true,
        "no-invalid-template-strings": true,
        "no-invalid-this": true,
        "no-irregular-whitespace": true,
        "no-mergeable-namespace": true,
        "no-parameter-properties": true,
        "no-shadowed-variable": [
            true,
            {
                "class": true,
                "enum": true,
                "function": true,
                "import": false,
                "interface": true,
                "namespace": true,
                "typeAlias": true,
                "typeParameter": true
            }
        ],
        "no-string-literal": true,
        "no-string-throw": true,
        "no-switch-case-fall-through": true,
        "no-this-assignment": true,
        "no-trailing-whitespace": true,
        "no-unnecessary-callback-wrapper": true,
        "no-unnecessary-initializer": true,
        "no-unused-expression": true,
        "no-var-keyword": true,
        "number-literal-format": true,
        "object-literal-key-quotes": [
            true,
            "as-needed"
        ],
        "object-literal-shorthand": true,
        "object-literal-sort-keys": [
            true,
            "ignore-case"
        ],
        "only-arrow-functions": [
            true,
            "allow-declarations",
            "allow-named-functions"
        ],
        "prefer-const": true,
        "prefer-for-of": true,
        "prefer-method-signature": true,
        "prefer-object-spread": true,
        "prefer-readonly": true,
        "prefer-template": true,
        "quotemark": [
            true,
            "double",
            "avoid-template"
        ],
        "restrict-plus-operands": true,
        "semicolon": true,
        "space-before-function-paren": [
            true,
            {
                "anonymous": "never",
                "asyncArrow": "always",
                "constructor": "never",
                "method": "never",
                "named": "never"
            }
        ],
        "switch-default": true,
        "switch-final-break": true,
        "trailing-comma": [
            true,
            {
                "multiline": "never",
                "singleline": "never"
            }
        ],
        "triple-equals": true,
        "typedef": [
            true,
            "array-destructuring",
            "call-signature",
            "member-variable-declaration",
            "object-destructuring",
            "parameter",
            "property-declaration"
        ],
        "typedef-whitespace": [
            true,
            {
                "call-signature": "nospace",
                "index-signature": "nospace",
                "parameter": "nospace",
                "property-declaration": "nospace",
                "variable-declaration": "nospace"
            },
            {
                "call-signature": "onespace",
                "index-signature": "onespace",
                "parameter": "onespace",
                "property-declaration": "onespace",
                "variable-declaration": "onespace"
            }
        ],
        "unified-signatures": true,
        "use-isnan": true,
        "whitespace": true
    }
}
Download .txt
gitextract_ckorfcxu/

├── .gitignore
├── .typeorm/
│   ├── connection/
│   │   ├── get-migration-data-source.ts
│   │   ├── get-typeorm-data-source.config.ts
│   │   └── get-typeorm-data-source.function.ts
│   ├── migrations/
│   │   └── 1629340508553-Initial.ts
│   └── seed/
│       ├── functions/
│       │   ├── main.function.ts
│       │   ├── seed-artists.function.ts
│       │   ├── seed-genres.function.ts
│       │   ├── seed-songs.function.ts
│       │   ├── seed-user-profile-attributes.function.ts
│       │   └── seed-users.function.ts
│       └── index.ts
├── .vscode/
│   ├── launch.json
│   └── settings.json
├── LICENSE
├── README.md
├── index.ts
├── ormconfig.example.json
├── package.json
├── src/
│   ├── constants/
│   │   └── SqlConstants.ts
│   ├── enums/
│   │   ├── QueryMode.ts
│   │   └── QueryWhereType.ts
│   ├── query/
│   │   ├── Query.ts
│   │   ├── QueryBuilderPart.ts
│   │   └── interfaces/
│   │       ├── IComparableQuery.ts
│   │       ├── IComparableQueryBase.ts
│   │       ├── IJoinedComparableQuery.ts
│   │       ├── IJoinedQuery.ts
│   │       ├── IQuery.ts
│   │       ├── IQueryBase.ts
│   │       ├── IQueryBuilderPart.ts
│   │       ├── IQueryInternal.ts
│   │       ├── ISelectQuery.ts
│   │       └── ISelectQueryInternal.ts
│   ├── repository/
│   │   ├── LinqRepository.ts
│   │   └── interfaces/
│   │       └── ILinqRepository.ts
│   └── types/
│       ├── ComparableValue.ts
│       ├── EntityBase.ts
│       ├── EntityConstructor.ts
│       ├── JoinedEntityType.ts
│       ├── QueryConditionOptions.ts
│       ├── QueryConditionOptionsInternal.ts
│       ├── QueryOrderOptions.ts
│       └── RepositoryOptions.ts
├── test/
│   ├── entities/
│   │   ├── artist.entity.ts
│   │   ├── genre.entity.ts
│   │   ├── song.entity.ts
│   │   ├── user-profile-attribute.entity.ts
│   │   └── user.entity.ts
│   ├── jasmine-ts.helper.js
│   ├── jasmine.json
│   └── scenarios/
│       └── query/
│           └── query.spec.ts
├── tsconfig.build.json
├── tsconfig.json
├── tsconfig.spec.json
└── tslint.json
Download .txt
SYMBOL INDEX (134 symbols across 38 files)

FILE: .typeorm/connection/get-typeorm-data-source.function.ts
  function getTypeormDataSource (line 4) | async function getTypeormDataSource(): Promise<DataSource> {

FILE: .typeorm/migrations/1629340508553-Initial.ts
  class Initial1629340508553 (line 3) | class Initial1629340508553 implements MigrationInterface {
    method up (line 6) | public async up(queryRunner: QueryRunner): Promise<void> {
    method down (line 18) | public async down(queryRunner: QueryRunner): Promise<void> {

FILE: .typeorm/seed/functions/main.function.ts
  function main (line 8) | async function main(): Promise<void> {

FILE: .typeorm/seed/functions/seed-artists.function.ts
  function seedArtists (line 4) | async function seedArtists(): Promise<void> {

FILE: .typeorm/seed/functions/seed-genres.function.ts
  function seedGenres (line 4) | async function seedGenres(): Promise<void> {

FILE: .typeorm/seed/functions/seed-songs.function.ts
  function seedSongs (line 4) | async function seedSongs(): Promise<void> {

FILE: .typeorm/seed/functions/seed-user-profile-attributes.function.ts
  function seedUserProfileAttributes (line 4) | async function seedUserProfileAttributes(): Promise<void> {

FILE: .typeorm/seed/functions/seed-users.function.ts
  function seedUsers (line 4) | async function seedUsers(): Promise<void> {

FILE: src/constants/SqlConstants.ts
  class SqlConstants (line 1) | class SqlConstants {

FILE: src/enums/QueryMode.ts
  type QueryMode (line 1) | enum QueryMode {

FILE: src/enums/QueryWhereType.ts
  type QueryWhereType (line 1) | enum QueryWhereType {

FILE: src/query/Query.ts
  class Query (line 22) | class Query<T extends EntityBase, R extends T | T[], P = T>
    method constructor (line 45) | public constructor(
    method getAction (line 63) | public get getAction(): () => Promise<R> {
    method query (line 67) | public get query(): SelectQueryBuilder<T> {
    method queryParts (line 71) | public get queryParts(): IQueryBuilderPart<T>[] {
    method selected (line 75) | public get selected(): string {
    method and (line 79) | public and<S extends Object, PP = P, RR = P>(propertySelector: (obj: P...
    method andAny (line 87) | public andAny<S extends Object>(
    method andNone (line 105) | public andNone<S extends Object>(
    method beginsWith (line 119) | public beginsWith(value: string, options?: QueryConditionOptions): IQu...
    method catch (line 130) | public async catch(rejected: (error: any) => void | Promise<any> | IQu...
    method contains (line 135) | public contains(value: string, options?: QueryConditionOptions): IQuer...
    method count (line 147) | public count(): Promise<number> {
    method endsWith (line 154) | public endsWith(value: string, options?: QueryConditionOptions): IQuer...
    method equal (line 165) | public equal(value: ComparableValue, options?: QueryConditionOptions):...
    method equalJoined (line 169) | public equalJoined(selector: (obj: P) => any, options?: QueryCondition...
    method from (line 175) | public from<F extends EntityBase>(
    method greaterThan (line 181) | public greaterThan(value: number | Date): IQuery<T, R, P> {
    method greaterThanJoined (line 185) | public greaterThanJoined(selector: (obj: P) => any): IQuery<T, R, P> {
    method greaterThanOrEqual (line 189) | public greaterThanOrEqual(value: number | Date): IQuery<T, R, P> {
    method greaterThanOrEqualJoined (line 193) | public greaterThanOrEqualJoined(selector: (obj: P) => any): IQuery<T, ...
    method groupBy (line 197) | public groupBy(propertySelector: (obj: P) => any): IQuery<T, R, P> {
    method in (line 207) | public in(include: string[] | number[], options?: QueryConditionOption...
    method include (line 219) | public include<S>(propertySelector: (obj: T) => JoinedEntityType<S>): ...
    method inSelected (line 223) | public inSelected<TI extends EntityBase, RI extends TI | TI[], PI1 = TI>(
    method isFalse (line 232) | public isFalse(): IQuery<T, R, P> {
    method isNotNull (line 238) | public isNotNull(): IQuery<T, R, P> {
    method isNull (line 246) | public isNull(): IQuery<T, R, P> {
    method isolatedAnd (line 254) | public isolatedAnd<S extends Object>(and: (query: IQuery<T, R, P>) => ...
    method isolatedOr (line 262) | public isolatedOr<S extends Object>(and: (query: IQuery<T, R, P>) => I...
    method isolatedWhere (line 270) | public isolatedWhere<S extends Object>(
    method isTrue (line 277) | public isTrue(): IQuery<T, R, P> {
    method join (line 283) | public join<S extends Object>(
    method joinAlso (line 289) | public joinAlso<S extends Object>(
    method lessThan (line 295) | public lessThan(value: number | Date): IQuery<T, R, P> {
    method lessThanJoined (line 299) | public lessThanJoined(selector: (obj: P) => any): IQuery<T, R, P> {
    method lessThanOrEqual (line 303) | public lessThanOrEqual(value: number | Date): IQuery<T, R, P> {
    method lessThanOrEqualJoined (line 307) | public lessThanOrEqualJoined(selector: (obj: P) => any): IQuery<T, R, ...
    method notEqual (line 311) | public notEqual(
    method notEqualJoined (line 317) | public notEqualJoined(selector: (obj: P) => any, options?: QueryCondit...
    method notIn (line 321) | public notIn(exclude: string[] | number[], options?: QueryConditionOpt...
    method notInSelected (line 333) | public notInSelected<TI extends EntityBase, RI extends TI | TI[], PI1 ...
    method or (line 342) | public or<S extends Object>(propertySelector: (obj: P) => S): ICompara...
    method orAny (line 350) | public orAny<S extends Object>(
    method orderBy (line 364) | public orderBy(propertySelector: (obj: P) => any, options?: QueryOrder...
    method orderByDescending (line 375) | public orderByDescending(
    method orNone (line 392) | public orNone<S extends Object>(
    method reset (line 406) | public reset(): IQuery<T, R, T> {
    method select (line 414) | public select(propertySelector: (obj: any) => any): ISelectQuery<T, R,...
    method skip (line 432) | public skip(skip: number): IQuery<T, R, P> {
    method take (line 442) | public take(limit: number): IQuery<T, R, P> {
    method then (line 452) | public async then(resolved: (results: R) => void | Promise<any>): Prom...
    method thenBy (line 457) | public thenBy(propertySelector: (obj: P) => any, options?: QueryOrderO...
    method thenByDescending (line 468) | public thenByDescending(
    method thenGroupBy (line 482) | public thenGroupBy(propertySelector: (obj: P) => any): IQuery<T, R, P> {
    method thenInclude (line 492) | public thenInclude<S extends Object>(
    method thenJoin (line 498) | public thenJoin<S extends Object>(
    method thenJoinAlso (line 504) | public thenJoinAlso<S extends Object>(
    method toPromise (line 510) | public toPromise(): Promise<R> {
    method usingBaseType (line 514) | public usingBaseType(): IQuery<T, R, T> {
    method where (line 520) | public where<S extends Object, F = T | P>(
    method whereAny (line 566) | public whereAny<S extends Object>(
    method whereNone (line 584) | public whereNone<S extends Object>(
    method addJoinCondition (line 598) | private addJoinCondition(
    method andOr (line 627) | private andOr<S extends Object, PP = P, RR = P>(
    method buildQuery (line 671) | private buildQuery(query: IQueryInternal<T, R, any>): SelectQueryBuild...
    method compileQueryParts (line 678) | private compileQueryParts<PT>(queryParts: IQueryBuilderPart<PT>[], bui...
    method completeGroupBy (line 686) | private completeGroupBy(
    method completeOrderBy (line 698) | private completeOrderBy(
    method completeJoinedWhere (line 717) | private completeJoinedWhere(
    method completeWhere (line 740) | private completeWhere(
    method createJoinCondition (line 881) | private createJoinCondition(joinConditionProperty: string): void {
    method escapeStringArray (line 916) | private escapeStringArray(array: string[]): void {
    method includeOrExcludeFromInnerQuery (line 924) | private includeOrExcludeFromInnerQuery<TI extends EntityBase, RI exten...
    method includePropertyUsingAlias (line 941) | private includePropertyUsingAlias<S extends Object>(
    method isolatedConditions (line 952) | private isolatedConditions<IP, IS>(
    method joinForeignEntity (line 994) | private joinForeignEntity<F extends Object>(
    method joinMultipleProperties (line 1015) | private joinMultipleProperties(
    method joinOrIncludePropertyUsingAlias (line 1060) | private joinOrIncludePropertyUsingAlias<S extends Object>(
    method joinPropertyUsingAlias (line 1135) | private joinPropertyUsingAlias<S extends Object>(
    method relationCount (line 1144) | private relationCount<S extends Object>(
    method setJoinIfNotCompare (line 1181) | private setJoinIfNotCompare(): void {

FILE: src/query/QueryBuilderPart.ts
  class QueryBuilderPart (line 5) | class QueryBuilderPart<T extends EntityBase> implements IQueryBuilderPar...
    method constructor (line 9) | public constructor(queryAction: (...params: any[]) => SelectQueryBuild...
    method queryAction (line 14) | public get queryAction(): (...params: any[]) => SelectQueryBuilder<T> {
    method queryParams (line 18) | public get queryParams(): any[] {

FILE: src/query/interfaces/IComparableQuery.ts
  type IComparableQuery (line 13) | interface IComparableQuery<T extends EntityBase, R extends T | T[], P = ...

FILE: src/query/interfaces/IComparableQueryBase.ts
  type IComparableQueryBase (line 8) | interface IComparableQueryBase<T extends EntityBase, R extends T | T[], ...

FILE: src/query/interfaces/IJoinedComparableQuery.ts
  type IJoinedComparableQuery (line 9) | interface IJoinedComparableQuery<T extends EntityBase, R extends T | T[]...

FILE: src/query/interfaces/IJoinedQuery.ts
  type IJoinedQuery (line 9) | interface IJoinedQuery<T extends EntityBase, R extends T | T[], P = T> e...

FILE: src/query/interfaces/IQuery.ts
  type IQuery (line 11) | interface IQuery<T extends EntityBase, R extends T | T[], P = T> extends...

FILE: src/query/interfaces/IQueryBase.ts
  type IQueryBase (line 11) | interface IQueryBase<T extends EntityBase, R extends T | T[], P = T> {

FILE: src/query/interfaces/IQueryBuilderPart.ts
  type IQueryBuilderPart (line 7) | interface IQueryBuilderPart<T extends EntityBase> {

FILE: src/query/interfaces/IQueryInternal.ts
  type IQueryInternal (line 9) | interface IQueryInternal<T extends EntityBase, R extends T | T[], P = T>...

FILE: src/query/interfaces/ISelectQuery.ts
  type ISelectQuery (line 7) | interface ISelectQuery<T extends EntityBase, R extends T | T[], P = T> {

FILE: src/query/interfaces/ISelectQueryInternal.ts
  type ISelectQueryInternal (line 5) | interface ISelectQueryInternal<T extends EntityBase, R extends T | T[], ...

FILE: src/repository/LinqRepository.ts
  class LinqRepository (line 13) | class LinqRepository<T extends EntityBase> implements ILinqRepository<T> {
    method constructor (line 25) | public constructor(
    method typeormRepository (line 48) | public get typeormRepository(): Repository<T> {
    method create (line 52) | public async create<E extends T | T[]>(entities: E): Promise<E> {
    method createQueryBuilder (line 78) | public createQueryBuilder(alias: string): SelectQueryBuilder<T> {
    method delete (line 82) | public async delete(entities: number | string | T | T[]): Promise<bool...
    method getAll (line 93) | public getAll(): IQuery<T, T[]> {
    method getById (line 102) | public getById(id: number | string): IQuery<T, T> {
    method getOne (line 114) | public getOne(): IQuery<T, T> {
    method update (line 124) | public async update<E extends T | T[]>(entities: E): Promise<E> {
    method upsert (line 128) | public async upsert<E extends T | T[]>(entities: E): Promise<E> {

FILE: src/repository/interfaces/ILinqRepository.ts
  type ILinqRepository (line 8) | interface ILinqRepository<T extends EntityBase> {

FILE: src/types/ComparableValue.ts
  type ComparableValue (line 1) | type ComparableValue = string | number | boolean | Date;

FILE: src/types/EntityBase.ts
  type EntityBase (line 1) | type EntityBase = {};

FILE: src/types/EntityConstructor.ts
  type EntityConstructor (line 3) | type EntityConstructor<T extends EntityBase> = { new (...params: any[]):...

FILE: src/types/JoinedEntityType.ts
  type JoinedEntityType (line 2) | type JoinedEntityType<J> = J | J[] | Promise<J[]> | Promise<J>;

FILE: src/types/QueryConditionOptions.ts
  type QueryConditionOptions (line 4) | interface QueryConditionOptions {

FILE: src/types/QueryConditionOptionsInternal.ts
  type QueryConditionOptionsInternal (line 1) | interface QueryConditionOptionsInternal {

FILE: src/types/QueryOrderOptions.ts
  type QueryOrderOptions (line 4) | interface QueryOrderOptions {

FILE: src/types/RepositoryOptions.ts
  type RepositoryOptions (line 6) | interface RepositoryOptions<T extends EntityBase> {

FILE: test/entities/artist.entity.ts
  class Artist (line 5) | class Artist {

FILE: test/entities/genre.entity.ts
  class Genre (line 4) | class Genre {

FILE: test/entities/song.entity.ts
  class Song (line 7) | class Song {

FILE: test/entities/user-profile-attribute.entity.ts
  class UserProfileAttribute (line 7) | class UserProfileAttribute {

FILE: test/entities/user.entity.ts
  class User (line 5) | class User {
Condensed preview — 56 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (156K chars).
[
  {
    "path": ".gitignore",
    "chars": 92,
    "preview": "**/node_modules/**\n**/*.log\n**/*.js\n!jasmine-ts.helper.js\n**/*.d.ts\normconfig.json\n.DS_Store"
  },
  {
    "path": ".typeorm/connection/get-migration-data-source.ts",
    "chars": 216,
    "preview": "import { DataSource } from \"typeorm\";\nimport { dataSourceOptions } from \"./get-typeorm-data-source.config\";\n\nconst dataS"
  },
  {
    "path": ".typeorm/connection/get-typeorm-data-source.config.ts",
    "chars": 623,
    "preview": "import { DataSourceOptions } from \"typeorm\";\nimport * as ormconfig from \"../../ormconfig.json\";\nimport { Artist } from \""
  },
  {
    "path": ".typeorm/connection/get-typeorm-data-source.function.ts",
    "chars": 296,
    "preview": "import { DataSource } from \"typeorm\";\nimport { dataSourceOptions } from \"./get-typeorm-data-source.config\";\n\nexport asyn"
  },
  {
    "path": ".typeorm/migrations/1629340508553-Initial.ts",
    "chars": 2610,
    "preview": "import {MigrationInterface, QueryRunner} from \"typeorm\";\n\nexport class Initial1629340508553 implements MigrationInterfac"
  },
  {
    "path": ".typeorm/seed/functions/main.function.ts",
    "chars": 738,
    "preview": "import { getTypeormDataSource } from \"../../connection/get-typeorm-data-source.function\";\nimport { seedArtists } from \"."
  },
  {
    "path": ".typeorm/seed/functions/seed-artists.function.ts",
    "chars": 647,
    "preview": "import { LinqRepository } from \"../../../src/repository/LinqRepository\";\nimport { Artist } from \"../../../test/entities/"
  },
  {
    "path": ".typeorm/seed/functions/seed-genres.function.ts",
    "chars": 618,
    "preview": "import { LinqRepository } from \"../../../src/repository/LinqRepository\";\nimport { Genre } from \"../../../test/entities/g"
  },
  {
    "path": ".typeorm/seed/functions/seed-songs.function.ts",
    "chars": 1247,
    "preview": "import { LinqRepository } from \"../../../src/repository/LinqRepository\";\nimport { Song } from \"../../../test/entities/so"
  },
  {
    "path": ".typeorm/seed/functions/seed-user-profile-attributes.function.ts",
    "chars": 678,
    "preview": "import { LinqRepository } from \"../../../src/repository/LinqRepository\";\nimport { UserProfileAttribute } from \"../../../"
  },
  {
    "path": ".typeorm/seed/functions/seed-users.function.ts",
    "chars": 449,
    "preview": "import { LinqRepository } from \"../../../src/repository/LinqRepository\";\nimport { User } from \"../../../test/entities/us"
  },
  {
    "path": ".typeorm/seed/index.ts",
    "chars": 223,
    "preview": "import { main } from \"./functions/main.function\";\n\nmain()\n    .then(() => {\n        console.log(\"Done.\");\n    })\n    .ca"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 527,
    "preview": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 167,
    "preview": "{\n    \"files.exclude\": {\n        \"**/*.d.ts\": true,\n        \"src/**/*.js\": true,\n        \"index.js\": true\n    },\n    \"ty"
  },
  {
    "path": "LICENSE",
    "chars": 1071,
    "preview": "MIT License\n\nCopyright (c) 2017 IRCraziestTaxi\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 35024,
    "preview": "# typeorm-linq-repository\nWraps TypeORM repository pattern and QueryBuilder using fluent, LINQ-style queries.\n\n## What's"
  },
  {
    "path": "index.ts",
    "chars": 568,
    "preview": "export { IComparableQuery } from \"./src/query/interfaces/IComparableQuery\";\nexport { IJoinedComparableQuery } from \"./sr"
  },
  {
    "path": "ormconfig.example.json",
    "chars": 224,
    "preview": "{\n    \"database\": \"typeorm-linq-repository-test\",\n    \"host\": \"localhost\",\n    \"migrations\": [\n        \".typeorm/migrati"
  },
  {
    "path": "package.json",
    "chars": 1788,
    "preview": "{\n  \"name\": \"typeorm-linq-repository\",\n  \"version\": \"2.0.2\",\n  \"description\": \"Wraps TypeORM repository pattern and Quer"
  },
  {
    "path": "src/constants/SqlConstants.ts",
    "chars": 846,
    "preview": "export class SqlConstants {\n    public static readonly OPERATOR_AND: \"AND\" = \"AND\";\n    public static readonly OPERATOR_"
  },
  {
    "path": "src/enums/QueryMode.ts",
    "chars": 316,
    "preview": "export enum QueryMode {\n    /**\n     * The default mode of a query in which results are returned.\n     */\n    Get = 0,\n "
  },
  {
    "path": "src/enums/QueryWhereType.ts",
    "chars": 193,
    "preview": "export enum QueryWhereType {\n    /**\n     * A normal comparison (not on a joined entity).\n     */\n    Normal = 0,\n    /*"
  },
  {
    "path": "src/query/Query.ts",
    "chars": 44881,
    "preview": "import { nameof } from \"ts-simple-nameof\";\nimport { Brackets, ObjectLiteral, SelectQueryBuilder, WhereExpression } from "
  },
  {
    "path": "src/query/QueryBuilderPart.ts",
    "chars": 763,
    "preview": "import { SelectQueryBuilder } from \"typeorm\";\nimport { EntityBase } from \"../types/EntityBase\";\nimport { IQueryBuilderPa"
  },
  {
    "path": "src/query/interfaces/IComparableQuery.ts",
    "chars": 6447,
    "preview": "import { ComparableValue } from \"../../types/ComparableValue\";\nimport { EntityBase } from \"../../types/EntityBase\";\nimpo"
  },
  {
    "path": "src/query/interfaces/IComparableQueryBase.ts",
    "chars": 807,
    "preview": "import { EntityBase } from \"../../types/EntityBase\";\nimport { JoinedEntityType } from \"../../types/JoinedEntityType\";\nim"
  },
  {
    "path": "src/query/interfaces/IJoinedComparableQuery.ts",
    "chars": 2567,
    "preview": "import { EntityBase } from \"../../types/EntityBase\";\nimport { QueryConditionOptions } from \"../../types/QueryConditionOp"
  },
  {
    "path": "src/query/interfaces/IJoinedQuery.ts",
    "chars": 984,
    "preview": "import { EntityBase } from \"../../types/EntityBase\";\nimport { IComparableQuery } from \"./IComparableQuery\";\nimport { IQu"
  },
  {
    "path": "src/query/interfaces/IQuery.ts",
    "chars": 9095,
    "preview": "import { EntityBase } from \"../../types/EntityBase\";\nimport { IComparableQuery } from \"./IComparableQuery\";\nimport { IQu"
  },
  {
    "path": "src/query/interfaces/IQueryBase.ts",
    "chars": 8727,
    "preview": "import { EntityBase } from \"../../types/EntityBase\";\nimport { JoinedEntityType } from \"../../types/JoinedEntityType\";\nim"
  },
  {
    "path": "src/query/interfaces/IQueryBuilderPart.ts",
    "chars": 315,
    "preview": "import { SelectQueryBuilder } from \"typeorm\";\nimport { EntityBase } from \"../../types/EntityBase\";\n\n/**\n * Represents a "
  },
  {
    "path": "src/query/interfaces/IQueryInternal.ts",
    "chars": 1008,
    "preview": "import { SelectQueryBuilder } from \"typeorm\";\nimport { EntityBase } from \"../../types/EntityBase\";\nimport { IQuery } fro"
  },
  {
    "path": "src/query/interfaces/ISelectQuery.ts",
    "chars": 353,
    "preview": "import { EntityBase } from \"../../types/EntityBase\";\n\n/**\n * Used to select the desired propery from the desired entity "
  },
  {
    "path": "src/query/interfaces/ISelectQueryInternal.ts",
    "chars": 485,
    "preview": "import { EntityBase } from \"../../types/EntityBase\";\nimport { IQueryInternal } from \"./IQueryInternal\";\nimport { ISelect"
  },
  {
    "path": "src/repository/LinqRepository.ts",
    "chars": 4773,
    "preview": "import { nameof } from \"ts-simple-nameof\";\nimport { DataSource, EntitySchema, Repository, SelectQueryBuilder } from \"typ"
  },
  {
    "path": "src/repository/interfaces/ILinqRepository.ts",
    "chars": 1741,
    "preview": "import { Repository, SelectQueryBuilder } from \"typeorm\";\nimport { IQuery } from \"../../query/interfaces/IQuery\";\nimport"
  },
  {
    "path": "src/types/ComparableValue.ts",
    "chars": 64,
    "preview": "export type ComparableValue = string | number | boolean | Date;\n"
  },
  {
    "path": "src/types/EntityBase.ts",
    "chars": 37,
    "preview": "export declare type EntityBase = {};\n"
  },
  {
    "path": "src/types/EntityConstructor.ts",
    "chars": 138,
    "preview": "import { EntityBase } from \"./EntityBase\";\n\nexport declare type EntityConstructor<T extends EntityBase> = { new (...para"
  },
  {
    "path": "src/types/JoinedEntityType.ts",
    "chars": 140,
    "preview": "// Support *-to-one, one-to-many, and lazy loaded relations.\nexport declare type JoinedEntityType<J> = J | J[] | Promise"
  },
  {
    "path": "src/types/QueryConditionOptions.ts",
    "chars": 220,
    "preview": "/**\n * Options for query conditions such as string case matching.\n */\nexport interface QueryConditionOptions {\n    /**\n "
  },
  {
    "path": "src/types/QueryConditionOptionsInternal.ts",
    "chars": 156,
    "preview": "export interface QueryConditionOptionsInternal {\n    beginsWith?: boolean;\n    endsWith?: boolean;\n    quoteString?: boo"
  },
  {
    "path": "src/types/QueryOrderOptions.ts",
    "chars": 245,
    "preview": "/**\n * Options for query ordering such as where to put nulls (supported by some databases).\n */\nexport interface QueryOr"
  },
  {
    "path": "src/types/RepositoryOptions.ts",
    "chars": 587,
    "preview": "import { EntityBase } from \"./EntityBase\";\n\n/**\n * Options for setting up the repository.\n */\nexport interface Repositor"
  },
  {
    "path": "test/entities/artist.entity.ts",
    "chars": 327,
    "preview": "import { Column, Entity, OneToMany, PrimaryColumn } from \"typeorm\";\nimport { Song } from \"./song.entity\";\n\n@Entity()\nexp"
  },
  {
    "path": "test/entities/genre.entity.ts",
    "chars": 206,
    "preview": "import { Column, Entity, PrimaryColumn } from \"typeorm\";\n\n@Entity()\nexport class Genre {\n    @PrimaryColumn()\n    public"
  },
  {
    "path": "test/entities/song.entity.ts",
    "chars": 719,
    "preview": "import { nameof } from \"ts-simple-nameof\";\nimport { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from \"typeorm"
  },
  {
    "path": "test/entities/user-profile-attribute.entity.ts",
    "chars": 701,
    "preview": "import { nameof } from \"ts-simple-nameof\";\nimport { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from \"typeorm"
  },
  {
    "path": "test/entities/user.entity.ts",
    "chars": 325,
    "preview": "import { Entity, OneToMany, PrimaryColumn } from \"typeorm\";\nimport { UserProfileAttribute } from \"./user-profile-attribu"
  },
  {
    "path": "test/jasmine-ts.helper.js",
    "chars": 91,
    "preview": "const { register } = require(\"ts-node\");\n\nregister({\n    project: \"tsconfig.spec.json\"\n});\n"
  },
  {
    "path": "test/jasmine.json",
    "chars": 192,
    "preview": "{\n    \"spec_files\": [\n        \"test/scenarios/**/*.spec.ts\"\n    ],\n    \"helpers\": [\n        \"test/jasmine-ts.helper.js\"\n"
  },
  {
    "path": "test/scenarios/query/query.spec.ts",
    "chars": 6628,
    "preview": "import { DataSource } from \"typeorm\";\nimport { getTypeormDataSource } from \"../../../.typeorm/connection/get-typeorm-dat"
  },
  {
    "path": "tsconfig.build.json",
    "chars": 95,
    "preview": "{\n    \"extends\": \"./tsconfig.json\",\n    \"exclude\": [\n        \".typeorm\",\n        \"test\"\n    ]\n}"
  },
  {
    "path": "tsconfig.json",
    "chars": 303,
    "preview": "{\n    \"compilerOptions\": {\n        \"module\": \"commonjs\",\n        \"target\": \"es6\",\n        \"lib\": [\n            \"es6\"\n   "
  },
  {
    "path": "tsconfig.spec.json",
    "chars": 158,
    "preview": "{\n    \"extends\": \"./tsconfig.json\",\n    \"compilerOptions\": {\n        \"sourceMap\": true,\n        \"inlineSourceMap\": true,"
  },
  {
    "path": "tslint.json",
    "chars": 4330,
    "preview": "{\n    \"rules\": {\n        \"adjacent-overload-signatures\": true,\n        \"align\": true,\n        \"array-type\": [\n          "
  }
]

About this extraction

This page contains the full source code of the IRCraziestTaxi/typeorm-linq-repository GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 56 files (144.3 KB), approximately 35.4k tokens, and a symbol index with 134 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!