From ce4c5e95e02fe36a89302ce97fbb7d2b0ef86717 Mon Sep 17 00:00:00 2001 From: marian2js Date: Mon, 24 Aug 2020 01:02:44 -0300 Subject: [PATCH] feat(mongodb): Add basic support for MongoDB --- packages/query-typeorm-mongo/.eslintrc.js | 15 + packages/query-typeorm-mongo/CHANGELOG.md | 277 ++++++++++++ packages/query-typeorm-mongo/README.md | 22 + .../__tests__/module.spec.ts | 12 + .../__tests__/providers.spec.ts | 18 + packages/query-typeorm-mongo/package.json | 57 +++ packages/query-typeorm-mongo/src/index.ts | 2 + packages/query-typeorm-mongo/src/module.ts | 18 + packages/query-typeorm-mongo/src/providers.ts | 23 + .../query-typeorm-mongo/src/services/index.ts | 1 + .../services/typeorm-query-mongo.service.ts | 400 ++++++++++++++++++ .../query-typeorm-mongo/tsconfig.build.json | 11 + packages/query-typeorm-mongo/tsconfig.json | 10 + 13 files changed, 866 insertions(+) create mode 100644 packages/query-typeorm-mongo/.eslintrc.js create mode 100644 packages/query-typeorm-mongo/CHANGELOG.md create mode 100644 packages/query-typeorm-mongo/README.md create mode 100644 packages/query-typeorm-mongo/__tests__/module.spec.ts create mode 100644 packages/query-typeorm-mongo/__tests__/providers.spec.ts create mode 100644 packages/query-typeorm-mongo/package.json create mode 100644 packages/query-typeorm-mongo/src/index.ts create mode 100644 packages/query-typeorm-mongo/src/module.ts create mode 100644 packages/query-typeorm-mongo/src/providers.ts create mode 100644 packages/query-typeorm-mongo/src/services/index.ts create mode 100644 packages/query-typeorm-mongo/src/services/typeorm-query-mongo.service.ts create mode 100644 packages/query-typeorm-mongo/tsconfig.build.json create mode 100644 packages/query-typeorm-mongo/tsconfig.json diff --git a/packages/query-typeorm-mongo/.eslintrc.js b/packages/query-typeorm-mongo/.eslintrc.js new file mode 100644 index 000000000..ae15c4d67 --- /dev/null +++ b/packages/query-typeorm-mongo/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + parserOptions: { + project: './tsconfig.build.json', + }, + rules: { + 'jest/expect-expect': [ + 'error', + { + assertFunctionNames: [ + 'expect', + ], + }, + ], + }, +}; diff --git a/packages/query-typeorm-mongo/CHANGELOG.md b/packages/query-typeorm-mongo/CHANGELOG.md new file mode 100644 index 000000000..1fe86455a --- /dev/null +++ b/packages/query-typeorm-mongo/CHANGELOG.md @@ -0,0 +1,277 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [0.18.1](https://github.com/doug-martin/nestjs-query/compare/v0.18.0...v0.18.1) (2020-08-14) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +# [0.18.0](https://github.com/doug-martin/nestjs-query/compare/v0.17.10...v0.18.0) (2020-08-11) + + +### Bug Fixes + +* **type:** Pin dev dependencies ([442db4c](https://github.com/doug-martin/nestjs-query/commit/442db4cd9b9d48d0c6a20209f0b44c4a314660ac)) + + +### Features + +* **typeorm:** Switch to use unioned queries for relations ([327c676](https://github.com/doug-martin/nestjs-query/commit/327c6760e3e1a7db6bb0f872928d0502345c925f)) + + + + + +## [0.17.10](https://github.com/doug-martin/nestjs-query/compare/v0.17.9...v0.17.10) (2020-08-01) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.17.8](https://github.com/doug-martin/nestjs-query/compare/v0.17.7...v0.17.8) (2020-07-28) + + +### Features + +* **graphql:** Allow specifying allowed comparisons on filterable fields ([ced2792](https://github.com/doug-martin/nestjs-query/commit/ced27920e5c2278c2a04c027a692e25b3306f6cb)) + + + + + +## [0.17.7](https://github.com/doug-martin/nestjs-query/compare/v0.17.6...v0.17.7) (2020-07-27) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.17.2](https://github.com/doug-martin/nestjs-query/compare/v0.17.1...v0.17.2) (2020-07-17) + + +### Bug Fixes + +* **typeorm:** Ensure record is entity instance when saving ([3cdbbaf](https://github.com/doug-martin/nestjs-query/commit/3cdbbaff11b18bcc5e6fd29fd182e2bd66b14f17)), closes [#380](https://github.com/doug-martin/nestjs-query/issues/380) + + + + + +# [0.17.0](https://github.com/doug-martin/nestjs-query/compare/v0.16.2...v0.17.0) (2020-07-16) + + +### Features + +* **aggregations:** Add aggregations to graphql ([af075d2](https://github.com/doug-martin/nestjs-query/commit/af075d2e93b6abbbfbe32afcc917350f803fadaa)) +* **aggregations,typeorm:** Add relation aggregation to typeorm ([2bf35a9](https://github.com/doug-martin/nestjs-query/commit/2bf35a92ce80b1f3026fd87cb62cad17eb6eff03)) +* **aggretations:** Add aggregations support to typeorm ([7233c23](https://github.com/doug-martin/nestjs-query/commit/7233c2397d0ac332e5209ab87ae62f5f555609d6)) + + + + + +## [0.16.2](https://github.com/doug-martin/nestjs-query/compare/v0.16.1...v0.16.2) (2020-07-09) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.16.1](https://github.com/doug-martin/nestjs-query/compare/v0.16.0...v0.16.1) (2020-07-07) + + +### Bug Fixes + +* **typeorm:** Fix import path in relation service [#363](https://github.com/doug-martin/nestjs-query/issues/363) ([0e6d484](https://github.com/doug-martin/nestjs-query/commit/0e6d484920960ed1966360a89af979230667b5f7)) + + + + + +# [0.16.0](https://github.com/doug-martin/nestjs-query/compare/v0.15.1...v0.16.0) (2020-07-05) + + +### Features + +* **typeorm:** Add support for filtering on relations ([aa8788c](https://github.com/doug-martin/nestjs-query/commit/aa8788cbbc0c95465e1633b57ca48c91b160038a)) + + + + + +## [0.15.1](https://github.com/doug-martin/nestjs-query/compare/v0.15.0...v0.15.1) (2020-06-27) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +# [0.15.0](https://github.com/doug-martin/nestjs-query/compare/v0.14.3...v0.15.0) (2020-06-23) + + +### Features + +* **graphql,connection:** Add totalCount to connections ([ed1e84a](https://github.com/doug-martin/nestjs-query/commit/ed1e84a2feb6f89c3b270fcbc1d0eaf6aec5e575)) + + + + + +## [0.14.2](https://github.com/doug-martin/nestjs-query/compare/v0.14.1...v0.14.2) (2020-06-19) + + +### Bug Fixes + +* **typeorm:** Allow using string based typeorm relations ([55c157d](https://github.com/doug-martin/nestjs-query/commit/55c157dbea9ce8c1186a2c2ea17f847857fd2226)) + + + + + +# [0.14.0](https://github.com/doug-martin/nestjs-query/compare/v0.13.2...v0.14.0) (2020-06-18) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +# [0.13.0](https://github.com/doug-martin/nestjs-query/compare/v0.12.0...v0.13.0) (2020-06-12) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +# [0.12.0](https://github.com/doug-martin/nestjs-query/compare/v0.11.8...v0.12.0) (2020-06-07) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.11.8](https://github.com/doug-martin/nestjs-query/compare/v0.11.7...v0.11.8) (2020-05-30) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.11.7](https://github.com/doug-martin/nestjs-query/compare/v0.11.6...v0.11.7) (2020-05-29) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.11.6](https://github.com/doug-martin/nestjs-query/compare/v0.11.5...v0.11.6) (2020-05-26) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.11.5](https://github.com/doug-martin/nestjs-query/compare/v0.11.4...v0.11.5) (2020-05-21) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.11.4](https://github.com/doug-martin/nestjs-query/compare/v0.11.3...v0.11.4) (2020-05-19) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.11.3](https://github.com/doug-martin/nestjs-query/compare/v0.11.2...v0.11.3) (2020-05-16) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.11.2](https://github.com/doug-martin/nestjs-query/compare/v0.11.1...v0.11.2) (2020-05-14) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.11.1](https://github.com/doug-martin/nestjs-query/compare/v0.11.0...v0.11.1) (2020-05-11) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +# [0.11.0](https://github.com/doug-martin/nestjs-query/compare/v0.10.2...v0.11.0) (2020-05-09) + + +### Features + +* **graphql:** Add graphql module ([282c421](https://github.com/doug-martin/nestjs-query/commit/282c421d0e6f67fe750fa6005f6cb7d960c8fbd0)) + + + + + +## [0.10.2](https://github.com/doug-martin/nestjs-query/compare/v0.10.1...v0.10.2) (2020-05-04) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +# [0.10.0](https://github.com/doug-martin/nestjs-query/compare/v0.9.0...v0.10.0) (2020-04-29) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +# [0.9.0](https://github.com/doug-martin/nestjs-query/compare/v0.8.9...v0.9.0) (2020-04-26) + + +### Features + +* **typeorm:** Add support for soft deletes ([2ab42fa](https://github.com/doug-martin/nestjs-query/commit/2ab42faee2802abae4d8496e2529b8eb23860ed4)) + + + + + +## [0.8.9](https://github.com/doug-martin/nestjs-query/compare/v0.8.8...v0.8.9) (2020-04-24) + +**Note:** Version bump only for package @nestjs-query/query-typeorm + + + + + +## [0.8.7](https://github.com/doug-martin/nestjs-query/compare/v0.8.6...v0.8.7) (2020-04-23) + +**Note:** Version bump only for package @nestjs-query/query-typeorm diff --git a/packages/query-typeorm-mongo/README.md b/packages/query-typeorm-mongo/README.md new file mode 100644 index 000000000..9589cee03 --- /dev/null +++ b/packages/query-typeorm-mongo/README.md @@ -0,0 +1,22 @@ +

+ Nestjs-query Logo +

+ +[![npm version](https://img.shields.io/npm/v/@nestjs-query/query-typeorm.svg)](https://www.npmjs.org/package/@nestjs-query/query-typeorm) +[![Test](https://github.com/doug-martin/nestjs-query/workflows/Test/badge.svg?branch=master)](https://github.com/doug-martin/nestjs-query/actions?query=workflow%3ATest+and+branch%3Amaster+) +[![Coverage Status](https://coveralls.io/repos/github/doug-martin/nestjs-query/badge.svg?branch=master)](https://coveralls.io/github/doug-martin/nestjs-query?branch=master) +[![Known Vulnerabilities](https://snyk.io/test/github/doug-martin/nestjs-query/badge.svg?targetFile=packages/query-typeorm/package.json)](https://snyk.io/test/github/doug-martin/nestjs-query?targetFile=packages/query-typeorm/package.json) + +# `@nestjs-query/query-typeorm` + +The `query-typeorm` package that provides an implementation of `@nestjs-query/core` `QueryService`, built on top of of [nestjs](https://nestjs.com/) and [typeorm](https://typeorm.io/). + +## Installation + +[Install Guide](https://doug-martin.github.io/nestjs-query/docs/introduction/install) + +## Getting Started + +The get started with the `@nestjs-query/query-typeorm` package checkout the [Getting Started](https://doug-martin.github.io/nestjs-query/docs/persistence/typeorm/getting-started) docs. + + diff --git a/packages/query-typeorm-mongo/__tests__/module.spec.ts b/packages/query-typeorm-mongo/__tests__/module.spec.ts new file mode 100644 index 000000000..a1cb5f40c --- /dev/null +++ b/packages/query-typeorm-mongo/__tests__/module.spec.ts @@ -0,0 +1,12 @@ +import { NestjsQueryTypeOrmMongoModule } from '../src'; + +describe('NestjsQueryTypeOrmMongoModule', () => { + it('should create a module', () => { + class TestEntity {} + const typeOrmModule = NestjsQueryTypeOrmMongoModule.forFeature([TestEntity]); + expect(typeOrmModule.imports).toHaveLength(1); + expect(typeOrmModule.module).toBe(NestjsQueryTypeOrmMongoModule); + expect(typeOrmModule.providers).toHaveLength(1); + expect(typeOrmModule.exports).toHaveLength(2); + }); +}); diff --git a/packages/query-typeorm-mongo/__tests__/providers.spec.ts b/packages/query-typeorm-mongo/__tests__/providers.spec.ts new file mode 100644 index 000000000..98f3ad0dd --- /dev/null +++ b/packages/query-typeorm-mongo/__tests__/providers.spec.ts @@ -0,0 +1,18 @@ +import { getQueryServiceToken } from '@nestjs-query/core'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { mock, instance } from 'ts-mockito'; +import { createTypeOrmMongoQueryServiceProviders } from '../src/providers'; +import { TypeOrmMongoQueryService } from '../src/services'; + +describe('createTypeOrmMongoQueryServiceProviders', () => { + it('should create a provider for the entity', () => { + class TestEntity {} + const mockRepo = mock>(Repository); + const providers = createTypeOrmMongoQueryServiceProviders([TestEntity]); + expect(providers).toHaveLength(1); + expect(providers[0].provide).toBe(getQueryServiceToken(TestEntity)); + expect(providers[0].inject).toEqual([getRepositoryToken(TestEntity)]); + expect(providers[0].useFactory(instance(mockRepo))).toBeInstanceOf(TypeOrmMongoQueryService); + }); +}); diff --git a/packages/query-typeorm-mongo/package.json b/packages/query-typeorm-mongo/package.json new file mode 100644 index 000000000..84888a012 --- /dev/null +++ b/packages/query-typeorm-mongo/package.json @@ -0,0 +1,57 @@ +{ + "name": "@nestjs-query/query-typeorm-mongo", + "version": "0.18.1", + "description": "Typeorm adapter for @nestjs-query/core", + "author": "doug-martin ", + "homepage": "https://github.com/doug-martin/nestjs-query#readme", + "license": "MIT", + "main": "dist/src/index.js", + "types": "dist/src/index.d.ts", + "directories": { + "lib": "dist/src", + "test": "__tests__" + }, + "files": [ + "dist/src/**" + ], + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@nestjs-query/core": "0.18.1", + "lodash.filter": "^4.6.0", + "lodash.omit": "^4.5.0" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0", + "@nestjs/typeorm": "^7.0.0", + "class-transformer": "^0.2.3 || ^0.3.0", + "typeorm": "^0.2.7" + }, + "devDependencies": { + "@nestjs/common": "7.4.2", + "@nestjs/testing": "7.4.2", + "@nestjs/typeorm": "7.1.0", + "@types/lodash.filter": "4.6.6", + "@types/lodash.omit": "4.5.6", + "@types/mongodb": "^3.5.26", + "class-transformer": "0.3.1", + "ts-mockito": "2.6.1", + "typeorm": "0.2.25", + "typescript": "4.0.2" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/doug-martin/nestjs-query.git", + "directory": "packages/query-typeorm-mongo" + }, + "scripts": { + "prepublishOnly": "npm run build", + "prebuild": "npm run clean", + "build": "tsc -p tsconfig.build.json", + "clean": "rm -rf ./dist && rm -rf tsconfig.tsbuildinfo" + }, + "bugs": { + "url": "https://github.com/doug-martin/nestjs-query/issues" + } +} diff --git a/packages/query-typeorm-mongo/src/index.ts b/packages/query-typeorm-mongo/src/index.ts new file mode 100644 index 000000000..34fe2dd43 --- /dev/null +++ b/packages/query-typeorm-mongo/src/index.ts @@ -0,0 +1,2 @@ +export { TypeOrmQueryService, TypeOrmQueryServiceOpts } from './services'; +export { NestjsQueryTypeOrmModule } from './module'; diff --git a/packages/query-typeorm-mongo/src/module.ts b/packages/query-typeorm-mongo/src/module.ts new file mode 100644 index 000000000..930d3ff13 --- /dev/null +++ b/packages/query-typeorm-mongo/src/module.ts @@ -0,0 +1,18 @@ +import { Class } from '@nestjs-query/core'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { DynamicModule } from '@nestjs/common'; +import { Connection, ConnectionOptions } from 'typeorm'; +import { createTypeOrmMongoQueryServiceProviders } from './providers'; + +export class NestjsQueryTypeOrmMongoModule { + static forFeature(entities: Class[], connection?: Connection | ConnectionOptions | string): DynamicModule { + const queryServiceProviders = createTypeOrmMongoQueryServiceProviders(entities, connection); + const typeOrmModule = TypeOrmModule.forFeature(entities, connection); + return { + imports: [typeOrmModule], + module: NestjsQueryTypeOrmMongoModule, + providers: [...queryServiceProviders], + exports: [...queryServiceProviders, typeOrmModule], + }; + } +} diff --git a/packages/query-typeorm-mongo/src/providers.ts b/packages/query-typeorm-mongo/src/providers.ts new file mode 100644 index 000000000..198d0cd1e --- /dev/null +++ b/packages/query-typeorm-mongo/src/providers.ts @@ -0,0 +1,23 @@ +import { Class, getQueryServiceToken } from '@nestjs-query/core'; +import { FactoryProvider } from '@nestjs/common'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { MongoRepository, Connection, ConnectionOptions } from 'typeorm'; +import { TypeOrmMongoQueryService } from './services'; + +function createTypeOrmMongoQueryServiceProvider( + EntityClass: Class, + connection?: Connection | ConnectionOptions | string, +): FactoryProvider { + return { + provide: getQueryServiceToken(EntityClass), + useFactory(repo: MongoRepository) { + return new TypeOrmMongoQueryService(repo); + }, + inject: [getRepositoryToken(EntityClass, connection)], + }; +} + +export const createTypeOrmMongoQueryServiceProviders = ( + entities: Class[], + connection?: Connection | ConnectionOptions | string, +): FactoryProvider[] => entities.map((entity) => createTypeOrmMongoQueryServiceProvider(entity, connection)); diff --git a/packages/query-typeorm-mongo/src/services/index.ts b/packages/query-typeorm-mongo/src/services/index.ts new file mode 100644 index 000000000..f8f577e5d --- /dev/null +++ b/packages/query-typeorm-mongo/src/services/index.ts @@ -0,0 +1 @@ +export * from './typeorm-query-mongo.service'; diff --git a/packages/query-typeorm-mongo/src/services/typeorm-query-mongo.service.ts b/packages/query-typeorm-mongo/src/services/typeorm-query-mongo.service.ts new file mode 100644 index 000000000..bef76fa91 --- /dev/null +++ b/packages/query-typeorm-mongo/src/services/typeorm-query-mongo.service.ts @@ -0,0 +1,400 @@ +import { + AggregateQuery, + AggregateResponse, + Class, + DeepPartial, + DeleteManyResponse, + Filter, + FilterComparisons, + Query, + QueryService, + UpdateManyResponse, +} from '@nestjs-query/core'; +import { Logger, MethodNotAllowedException, NotFoundException } from '@nestjs/common'; +import { FindConditions, MongoRepository } from 'typeorm'; + +export interface TypeOrmMongoQueryServiceOpts { + useSoftDelete?: boolean; +} + +/** + * Base class for all query services that use a `typeorm` Repository. + * + * @example + * + * ```ts + * @QueryService(TodoItemEntity) + * export class TodoItemService extends TypeOrmQueryService { + * constructor( + * @InjectRepository(TodoItemEntity) repo: Repository, + * ) { + * super(repo); + * } + * } + * ``` + */ +export class TypeOrmMongoQueryService implements QueryService { + protected readonly logger = new Logger(TypeOrmMongoQueryService.name); + + readonly useSoftDelete: boolean; + + constructor(readonly repo: MongoRepository, opts?: TypeOrmMongoQueryServiceOpts) { + this.useSoftDelete = opts?.useSoftDelete ?? false; + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + get EntityClass(): Class { + return this.repo.target as Class; + } + + protected buildExpression(filter: Filter): FindConditions { + const where: FindConditions = {}; + + Object.entries(filter).forEach(([key, value]) => { + if (Array.isArray(value)) { + where[`$${key}`] = value.map((subFilter) => this.buildExpression(subFilter)); + } else if (value) { + Object.entries(value).forEach(([fieldKey, fieldValue]) => { + let mongoOperator: string | undefined; + if (['eq', 'gt', 'gte', 'lt', 'lte', 'in'].includes(fieldKey)) { + mongoOperator = `$${fieldKey}`; + } + if (fieldKey === 'neq') { + mongoOperator = '$ne'; + } + if (fieldKey === 'notIn') { + mongoOperator = '$nin'; + } + if (mongoOperator) { + where[key] = { + [mongoOperator]: fieldValue, + }; + } else { + this.logger.error(`Operator ${fieldKey} not supported yet`); + } + }); + } + }); + + return where; + } + + /** + * Query for multiple entities, using a Query from `@nestjs-query/core`. + * + * @example + * ```ts + * const todoItems = await this.service.query({ + * filter: { title: { eq: 'Foo' } }, + * paging: { limit: 10 }, + * sorting: [{ field: "create", direction: SortDirection.DESC }], + * }); + * ``` + * @param query - The Query used to filter, page, and sort rows. + */ + query(query: Query): Promise { + return this.repo.find({ + where: this.buildExpression(query.filter || {}), + skip: query.paging?.offset, + take: query.paging?.limit, + }); + } + + aggregate(filter: Filter, aggregate: AggregateQuery): Promise> { + throw new Error('Not implemented yet'); + } + + count(filter: Filter): Promise { + return this.repo.count(this.buildExpression(filter)); + } + + /** + * Find an entity by it's `id`. + * + * @example + * ```ts + * const todoItem = await this.service.findById(1); + * ``` + * @param id - The id of the record to find. + */ + async findById(id: string | number): Promise { + return this.repo.findOne(id); + } + + /** + * Gets an entity by it's `id`. If the entity is not found a rejected promise is returned. + * + * @example + * ```ts + * try { + * const todoItem = await this.service.getById(1); + * } catch(e) { + * console.error('Unable to find entity with id = 1'); + * } + * ``` + * @param id - The id of the record to find. + */ + async getById(id: string | number): Promise { + const entity = await this.findById(id); + if (!entity) { + throw new NotFoundException(`Unable to find ${this.EntityClass.name} with id: ${id}`); + } + return entity; + } + + /** + * Creates a single entity. + * + * @example + * ```ts + * const todoItem = await this.service.createOne({title: 'Todo Item', completed: false }); + * ``` + * @param record - The entity to create. + */ + async createOne>(record: C): Promise { + const entity = await this.ensureIsEntityAndDoesNotExist(record); + return this.repo.save(entity); + } + + /** + * Create multiple entities. + * + * @example + * ```ts + * const todoItem = await this.service.createMany([ + * {title: 'Todo Item 1', completed: false }, + * {title: 'Todo Item 2', completed: true }, + * ]); + * ``` + * @param records - The entities to create. + */ + async createMany>(records: C[]): Promise { + const entities = await Promise.all(records.map((r) => this.ensureIsEntityAndDoesNotExist(r))); + return this.repo.save(entities); + } + + /** + * Update an entity. + * + * @example + * ```ts + * const updatedEntity = await this.service.updateOne(1, { completed: true }); + * ``` + * @param id - The `id` of the record. + * @param update - A `Partial` of the entity with fields to update. + */ + async updateOne>(id: number | string, update: U): Promise { + this.ensureIdIsNotPresent(update); + const entity = await this.repo.findOneOrFail(id); + return this.repo.save(this.repo.merge(entity, update)); + } + + /** + * Update multiple entities with a `@nestjs-query/core` Filter. + * + * @example + * ```ts + * const { updatedCount } = await this.service.updateMany( + * { completed: true }, // the update to apply + * { title: { eq: 'Foo Title' } } // Filter to find records to update + * ); + * ``` + * @param update - A `Partial` of entity with the fields to update + * @param filter - A Filter used to find the records to update + */ + async updateMany>(update: U, filter: Filter): Promise { + this.ensureIdIsNotPresent(update); + const res = await this.repo.updateMany(this.buildExpression(filter), { $set: update }); + return { updatedCount: res.result.nModified || 0 }; + } + + /** + * Delete an entity by `id`. + * + * @example + * + * ```ts + * const deletedTodo = await this.service.deleteOne(1); + * ``` + * + * @param id - The `id` of the entity to delete. + */ + async deleteOne(id: string | number): Promise { + const entity = await this.repo.findOneOrFail(id); + if (this.useSoftDelete) { + return this.repo.softRemove(entity); + } + return this.repo.remove(entity); + } + + /** + * Delete multiple records with a `@nestjs-query/core` `Filter`. + * + * @example + * + * ```ts + * const { deletedCount } = this.service.deleteMany({ + * created: { lte: new Date('2020-1-1') } + * }); + * ``` + * + * @param filter - A `Filter` to find records to delete. + */ + async deleteMany(filter: Filter): Promise { + if (this.useSoftDelete) { + const res = await this.repo.softDelete(this.buildExpression(filter)); + return { deletedCount: res.affected || 0 }; + } + const res = await this.repo.deleteMany(this.buildExpression(filter)); + return { deletedCount: res.deletedCount || 0 }; + } + + /** + * Restore an entity by `id`. + * + * @example + * + * ```ts + * const restoredTodo = await this.service.restoreOne(1); + * ``` + * + * @param id - The `id` of the entity to restore. + */ + async restoreOne(id: string | number): Promise { + this.ensureSoftDeleteEnabled(); + await this.repo.restore(id); + return this.getById(id); + } + + /** + * Restores multiple records with a `@nestjs-query/core` `Filter`. + * + * @example + * + * ```ts + * const { updatedCount } = this.service.restoreMany({ + * created: { lte: new Date('2020-1-1') } + * }); + * ``` + * + * @param filter - A `Filter` to find records to delete. + */ + async restoreMany(filter: Filter): Promise { + this.ensureSoftDeleteEnabled(); + const res = await this.repo.restore(this.buildExpression(filter)); + return { updatedCount: res.affected || 0 }; + } + + private async ensureIsEntityAndDoesNotExist(e: DeepPartial): Promise { + if (!(e instanceof this.EntityClass)) { + return this.ensureEntityDoesNotExist(this.repo.create(e)); + } + return this.ensureEntityDoesNotExist(e); + } + + private async ensureEntityDoesNotExist(e: Entity): Promise { + if (this.repo.hasId(e)) { + const found = await this.repo.findOne(this.repo.getId(e)); + if (found) { + throw new Error('Entity already exists'); + } + } + return e; + } + + private ensureIdIsNotPresent(e: DeepPartial): void { + if (this.repo.hasId((e as unknown) as Entity)) { + throw new Error('Id cannot be specified when updating'); + } + } + + private ensureSoftDeleteEnabled(): void { + if (!this.useSoftDelete) { + throw new MethodNotAllowedException(`Restore not allowed for non soft deleted entity ${this.EntityClass.name}.`); + } + } + + queryRelations( + RelationClass: Class, + relationName: string, + dto: Entity, + query: Query, + ): Promise; + queryRelations( + RelationClass: Class, + relationName: string, + dtos: Entity[], + query: Query, + ): Promise>; + queryRelations(RelationClass: any, relationName: any, dtos: any, query: any) { + throw new Error('Not implemented yet'); + } + + aggregateRelations( + RelationClass: Class, + relationName: string, + dto: Entity, + filter: Filter, + aggregate: AggregateQuery, + ): Promise>; + aggregateRelations( + RelationClass: Class, + relationName: string, + dtos: Entity[], + filter: Filter, + aggregate: AggregateQuery, + ): Promise>>; + aggregateRelations(RelationClass: any, relationName: any, dtos: any, filter: any, aggregate: any) { + throw new Error('Not implemented yet'); + } + + countRelations( + RelationClass: Class, + relationName: string, + dto: Entity, + filter: Filter, + ): Promise; + countRelations( + RelationClass: Class, + relationName: string, + dto: Entity[], + filter: Filter, + ): Promise>; + countRelations(RelationClass: any, relationName: any, dto: any, filter: any) { + throw new Error('Not implemented yet'); + } + + findRelation( + RelationClass: Class, + relationName: string, + dto: Entity, + ): Promise; + findRelation( + RelationClass: Class, + relationName: string, + dtos: Entity[], + ): Promise>; + findRelation(RelationClass: any, relationName: any, dtos: any) { + throw new Error('Not implemented yet'); + } + + addRelations(relationName: string, id: string | number, relationIds: (string | number)[]): Promise { + throw new Error('Not implemented yet'); + } + + setRelation(relationName: string, id: string | number, relationId: string | number): Promise { + throw new Error('Not implemented yet'); + } + + removeRelations( + relationName: string, + id: string | number, + relationIds: (string | number)[], + ): Promise { + throw new Error('Not implemented yet'); + } + + removeRelation(relationName: string, id: string | number, relationId: string | number): Promise { + throw new Error('Not implemented yet'); + } +} diff --git a/packages/query-typeorm-mongo/tsconfig.build.json b/packages/query-typeorm-mongo/tsconfig.build.json new file mode 100644 index 000000000..0a6e60a60 --- /dev/null +++ b/packages/query-typeorm-mongo/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "." + }, + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/packages/query-typeorm-mongo/tsconfig.json b/packages/query-typeorm-mongo/tsconfig.json new file mode 100644 index 000000000..584a91ca1 --- /dev/null +++ b/packages/query-typeorm-mongo/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src", + "__tests__" + ], + "exclude": [ + "dist" + ] +}