Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: constraint operations optional attribute types #245

Merged
merged 10 commits into from
Jun 2, 2024
File renamed without changes.
11 changes: 7 additions & 4 deletions examples/nestjs-mongoose-book-manager/src/book.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { AuditOptions } from '../../../dist/util/operation-options';
import { AudioBook, Book, PaperBook } from './book';
import { AudioBookSchema, BookSchema, PaperBookSchema } from './book.schemas';

type SoftDeleteAllOptions = DeleteAllOptions & AuditOptions;
type SoftDeleteAllOptions = DeleteAllOptions<Book> & AuditOptions;
type SoftDeleteByIdOptions = DeleteByIdOptions & AuditOptions;

@Injectable()
Expand All @@ -24,9 +24,12 @@ export class MongooseBookRepository
constructor(@InjectConnection() connection: Connection) {
super(
{
Default: { type: Book, schema: BookSchema },
PaperBook: { type: PaperBook, schema: PaperBookSchema },
AudioBook: { type: AudioBook, schema: AudioBookSchema },
type: Book,
schema: BookSchema,
subtypes: [
{ type: PaperBook, schema: PaperBookSchema },
{ type: AudioBook, schema: AudioBookSchema },
],
},
connection,
);
Expand Down
1 change: 1 addition & 0 deletions examples/nestjs-mongoose-book-manager/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5551,6 +5551,7 @@ which@^2.0.1:
isexe "^2.0.0"

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
name wrap-ansi-cjs
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand Down
10 changes: 2 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,10 @@
"name": "monguito",
"version": "5.1.1",
"description": "MongoDB Abstract Repository implementation for Node.js",
"author": {
"name": "Josu Martinez",
"email": "josu.martinez@gmail.com"
},
"author": "Josu Martinez <josu.martinez@gmail.com>",
"license": "MIT",
"homepage": "https://github.com/josuto/monguito#readme",
"repository": {
"type": "git",
"url": "https://github.com/josuto/monguito.git"
},
"repository": "https://github.com/josuto/monguito.git",
"bugs": {
"url": "https://github.com/josuto/monguito/issues",
"email": "josu.martinez@gmail.com"
Expand Down
17 changes: 3 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MongooseTransactionalRepository } from './mongoose.transactional-reposi
import { PartialEntityWithId, Repository } from './repository';
import { TransactionalRepository } from './transactional-repository';
import { Auditable, AuditableClass, isAuditable } from './util/audit';
import { DomainModel, DomainTree } from './util/domain-model';
import { Entity } from './util/entity';
import {
IllegalArgumentException,
Expand All @@ -26,24 +27,16 @@ import {
SchemaPlugin,
} from './util/schema';
import { runInTransaction, TransactionOptions } from './util/transaction';
import {
AbsConstructor,
Constructor,
SubtypeData,
SubtypeMap,
SupertypeData,
TypeMap,
} from './util/type-map';

export {
AbsConstructor,
Auditable,
AuditableClass,
AuditableSchema,
BaseSchema,
Constructor,
DeleteAllOptions,
DeleteByIdOptions,
DomainModel,
DomainTree,
Entity,
extendSchema,
FindAllOptions,
Expand All @@ -60,12 +53,8 @@ export {
SaveOptions,
SchemaOptions,
SchemaPlugin,
SubtypeData,
SubtypeMap,
SupertypeData,
TransactionalRepository,
TransactionOptions,
TypeMap,
UndefinedConstructorException,
ValidationException,
};
79 changes: 22 additions & 57 deletions src/mongoose.repository.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import mongoose, {
Connection,
FilterQuery,
HydratedDocument,
Model,
UpdateQuery,
} from 'mongoose';
import { Optional } from 'typescript-optional';
import { PartialEntityWithId, Repository } from './repository';
import { isAuditable } from './util/audit';
import { DomainModel, DomainTree } from './util/domain-model';
import { Entity } from './util/entity';
import {
IllegalArgumentException,
Expand All @@ -20,45 +22,43 @@ import {
FindOneOptions,
SaveOptions,
} from './util/operation-options';
import { Constructor, TypeMap, TypeMapImpl } from './util/type-map';

/**
* Abstract Mongoose-based implementation of the {@link Repository} interface.
*/
export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
implements Repository<T>
{
private readonly typeMap: TypeMapImpl<T>;
private readonly domainTree: DomainTree<T>;
protected readonly entityModel: Model<T>;

/**
* Sets up the underlying configuration to enable database operation execution.
* @param {TypeMap<T>} typeMap a map of domain object types supported by this repository.
* @param {DomainModel<T>} domainModel the domain model supported by this repository.
* @param {Connection=} connection (optional) a MongoDB instance connection.
*/
protected constructor(
typeMap: TypeMap<T>,
domainModel: DomainModel<T>,
protected readonly connection?: Connection,
) {
this.typeMap = new TypeMapImpl(typeMap);
this.domainTree = new DomainTree(domainModel);
this.entityModel = this.createEntityModel(connection);
}

private createEntityModel(connection?: Connection) {
let entityModel;
const supertypeData = this.typeMap.getSupertypeData();
if (connection) {
entityModel = connection.model<T>(
supertypeData.type.name,
supertypeData.schema,
this.domainTree.type.name,
this.domainTree.schema,
);
} else {
entityModel = mongoose.model<T>(
supertypeData.type.name,
supertypeData.schema,
this.domainTree.type.name,
this.domainTree.schema,
);
}
for (const subtypeData of this.typeMap.getSubtypesData()) {
for (const subtypeData of this.domainTree.getSubtypeTree()) {
entityModel.discriminator(subtypeData.type.name, subtypeData.schema);
}
return entityModel;
Expand All @@ -69,10 +69,6 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
id: string,
options?: FindByIdOptions,
): Promise<Optional<S>> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
if (!id) throw new IllegalArgumentException('The given ID must be valid');
const document = await this.entityModel
.findById(id)
Expand All @@ -83,32 +79,17 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>

/** @inheritdoc */
async findOne<S extends T>(
filters: any,
options?: FindOneOptions,
options?: FindOneOptions<S>,
): Promise<Optional<S>> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
if (filters)
console.warn(
'Since v5.0.1 the "filters" parameter is deprecated. Use "options.filters" instead.',
);
if (!filters && !options?.filters)
throw new IllegalArgumentException('Missing search criteria (filters)');
const document = await this.entityModel
.findOne(options?.filters ?? filters)
.findOne(options?.filters ?? undefined)
.session(options?.session ?? null)
.exec();
return Optional.ofNullable(this.instantiateFrom(document) as S);
}

/** @inheritdoc */
async findAll<S extends T>(options?: FindAllOptions): Promise<S[]> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
async findAll<S extends T>(options?: FindAllOptions<S>): Promise<S[]> {
if (options?.pageable?.pageNumber && options?.pageable?.pageNumber < 0) {
throw new IllegalArgumentException(
'The given page number must be a positive number',
Expand All @@ -124,7 +105,7 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
const pageNumber = options?.pageable?.pageNumber ?? 0;
try {
const documents = await this.entityModel
.find(options?.filters)
.find(options?.filters as FilterQuery<S>)
.skip(pageNumber > 0 ? (pageNumber - 1) * offset : 0)
.limit(offset)
.sort(options?.sortBy)
Expand All @@ -144,10 +125,6 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
entity: S | PartialEntityWithId<S>,
options?: SaveOptions,
): Promise<S> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
if (!entity)
throw new IllegalArgumentException(
'The given entity cannot be null or undefined',
Expand Down Expand Up @@ -183,10 +160,6 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
entity: S,
options?: SaveOptions,
): Promise<S> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
if (!entity)
throw new IllegalArgumentException(
'The given entity cannot be null or undefined',
Expand All @@ -212,12 +185,12 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
entity: S | PartialEntityWithId<S>,
): void {
const entityClassName = entity['constructor']['name'];
if (!this.typeMap.has(entityClassName)) {
if (!this.domainTree.has(entityClassName)) {
throw new IllegalArgumentException(
`The entity with name ${entityClassName} is not included in the setup of the custom repository`,
);
}
const isSubtype = entityClassName !== this.typeMap.getSupertypeName();
const isSubtype = entityClassName !== this.domainTree.getSupertypeName();
const hasEntityDiscriminatorKey = '__t' in entity;
if (isSubtype && !hasEntityDiscriminatorKey) {
entity['__t'] = entityClassName;
Expand Down Expand Up @@ -246,10 +219,6 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
entity: PartialEntityWithId<S>,
options?: SaveOptions,
): Promise<S> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
if (!entity)
throw new IllegalArgumentException('The given entity must be valid');
const document = await this.entityModel
Expand Down Expand Up @@ -281,10 +250,6 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>

/** @inheritdoc */
async deleteById(id: string, options?: DeleteByIdOptions): Promise<boolean> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
if (!id) throw new IllegalArgumentException('The given ID must be valid');
const isDeleted = await this.entityModel.findByIdAndDelete(id, {
session: options?.session,
Expand All @@ -303,15 +268,15 @@ export abstract class MongooseRepository<T extends Entity & UpdateQuery<T>>
): S | null {
if (!document) return null;
const entityKey = document.get('__t');
const constructor: Constructor<S> | undefined = entityKey
? (this.typeMap.getSubtypeData(entityKey)?.type as Constructor<S>)
: (this.typeMap.getSupertypeData().type as Constructor<S>);
const constructor = entityKey
? this.domainTree.getSubtypeConstructor(entityKey)
: this.domainTree.getSupertypeConstructor();
if (constructor) {
// safe instantiation as no abstract class instance can be stored in the first place
return new constructor(document.toObject());
return new constructor(document.toObject()) as S;
}
throw new UndefinedConstructorException(
`There is no registered instance constructor for the document with ID ${document.id}`,
`There is no registered instance constructor for the document with ID ${document.id} or the constructor is abstract`,
);
}
}
26 changes: 5 additions & 21 deletions src/mongoose.transactional-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { ClientSession, Connection, UpdateQuery } from 'mongoose';
import { MongooseRepository } from './mongoose.repository';
import { PartialEntityWithId } from './repository';
import { TransactionalRepository } from './transactional-repository';
import { DomainModel } from './util/domain-model';
import { Entity } from './util/entity';
import { IllegalArgumentException } from './util/exceptions';
import {
DeleteAllOptions,
SaveAllOptions,
SaveOptions,
} from './util/operation-options';
import { runInTransaction } from './util/transaction';
import { TypeMap } from './util/type-map';

/**
* Abstract Mongoose-based implementation of the {@link TransactionalRepository} interface.
Expand All @@ -23,22 +22,18 @@ export abstract class MongooseTransactionalRepository<
{
/**
* Sets up the underlying configuration to enable database operation execution.
* @param {TypeMap<T>} typeMap a map of domain object types supported by this repository.
* @param {DomainModel<T>} domainModel the domain model supported by this repository.
* @param {Connection=} connection (optional) a MongoDB instance connection.
*/
protected constructor(typeMap: TypeMap<T>, connection?: Connection) {
super(typeMap, connection);
protected constructor(domainModel: DomainModel<T>, connection?: Connection) {
super(domainModel, connection);
}

/** @inheritdoc */
async saveAll<S extends T>(
entities: (S | PartialEntityWithId<S>)[],
options?: SaveAllOptions,
): Promise<S[]> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
return await runInTransaction(
async (session: ClientSession) =>
await Promise.all(
Expand All @@ -55,14 +50,7 @@ export abstract class MongooseTransactionalRepository<
}

/** @inheritdoc */
async deleteAll(options?: DeleteAllOptions): Promise<number> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
if (options?.filters === null) {
throw new IllegalArgumentException('Null filters are disallowed');
}
async deleteAll<S extends T>(options?: DeleteAllOptions<S>): Promise<number> {
return await runInTransaction(
async (session: ClientSession) =>
(await this.entityModel.deleteMany(options?.filters, { session }))
Expand All @@ -76,10 +64,6 @@ export abstract class MongooseTransactionalRepository<
entity: PartialEntityWithId<S>,
options?: SaveOptions,
): Promise<S> {
if (options?.connection)
console.warn(
'Since v5.0.1 "options.connection" is deprecated as is of no longer use.',
);
const updateOperation = super.update.bind(this);
return await runInTransaction(
async (session: ClientSession) =>
Expand Down
Loading
Loading