@abejarano/ts-mongodb-criteria 1.11.2 → 1.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -181,8 +181,8 @@ Use the `collection` argument inside `ensureIndexes` to avoid recursion.
181
181
 
182
182
  MongoRepository provides ready-to-use public methods for repositories that extend it:
183
183
 
184
- - `list(criteria, fieldsToExclude?)` for paginated queries
185
- - `many(filter, options?)` to fetch multiple entities matching a filter, with optional `{ transaction?, fieldsToExclude?, sort? }`
184
+ - `list(criteria, transaction?)` for paginated queries
185
+ - `many(filter, options?)` to fetch multiple entities matching a filter, with optional `{ transaction?, sort? }`
186
186
  - `one(filter, transaction?)` to fetch a single entity
187
187
  - `upsert(entity, transaction?)` to persist an aggregate
188
188
  Internal helpers are private, so repositories should call these public methods
@@ -241,7 +241,7 @@ async deactivateUser(id: string, tx: MongoTransaction): Promise<void> {
241
241
  MongoDB transactions require a replica set or a sharded cluster; standalone
242
242
  MongoDB instances do not support them. This initial API applies the transaction
243
243
  context to `upsert`, `delete`, protected `updateOne`, `one`, `list`, and `many`.
244
- Pass `tx` as the third argument to `list` after `fieldsToExclude`, and inside the options object (`{ transaction: tx }`) to `many`.
244
+ Pass `tx` as the second argument to `list`, and inside the options object (`{ transaction: tx }`) to `many`.
245
245
 
246
246
  **Your First Query in 30 Seconds:**
247
247
 
@@ -388,7 +388,7 @@ const criteria = new Criteria(Filters.fromValues(filters), Order.none())
388
388
 
389
389
  ## 🧪 Testing
390
390
 
391
- The library includes comprehensive test coverage (46/46 tests passing).
391
+ The library includes comprehensive test coverage (45/45 tests passing).
392
392
 
393
393
  ```bash
394
394
  # Run all tests
@@ -2,9 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AggregateRoot = void 0;
4
4
  class AggregateRoot {
5
- constructor(id) {
6
- this.aggregateId = id;
7
- }
8
5
  getId() {
9
6
  return this.aggregateId;
10
7
  }
@@ -2,16 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Criteria = void 0;
4
4
  class Criteria {
5
- constructor(filters, order, limit, offset) {
5
+ constructor(filters, order, limit = 10, currentPage = 1) {
6
+ if (!Number.isInteger(limit) || limit <= 0) {
7
+ throw new Error("CRITERIA_LIMIT_MUST_BE_A_POSITIVE_INTEGER");
8
+ }
9
+ if (!Number.isInteger(currentPage) || currentPage <= 0) {
10
+ throw new Error("CRITERIA_PAGE_MUST_BE_A_POSITIVE_INTEGER");
11
+ }
6
12
  this.filters = filters;
7
13
  this.order = order;
8
14
  this.limit = limit;
9
- this.currentPage = offset;
10
- if (offset && limit) {
11
- this.offset = offset > 0 ? (offset - 1) * limit : 0;
12
- }
13
- else
14
- this.offset = undefined;
15
+ this.currentPage = currentPage;
16
+ this.offset = (currentPage - 1) * limit;
15
17
  }
16
18
  hasFilters() {
17
19
  return this.filters.filters.length > 0;
@@ -27,8 +27,8 @@ class MongoCriteriaConverter {
27
27
  sort: criteria.order.hasOrder()
28
28
  ? this.generateSort(criteria.order)
29
29
  : { _id: -1 },
30
- skip: criteria.offset || 0,
31
- limit: criteria.limit || 0,
30
+ skip: criteria.offset,
31
+ limit: criteria.limit,
32
32
  };
33
33
  }
34
34
  generateFilter(filters) {
@@ -20,10 +20,10 @@ class MongoRepository {
20
20
  if (!result) {
21
21
  return null;
22
22
  }
23
- return this.aggregateRootClass.fromPrimitives({
24
- ...result,
25
- id: result._id.toString(),
26
- });
23
+ const { _id, ...primitives } = result;
24
+ const entity = this.aggregateRootClass.fromPrimitives(primitives);
25
+ entity.assignId(_id.toString());
26
+ return entity;
27
27
  }
28
28
  /**
29
29
  *
@@ -32,23 +32,6 @@ class MongoRepository {
32
32
  */
33
33
  async many(filter, options) {
34
34
  const collection = await this.collection();
35
- const session = options?.transaction
36
- ? MongoTransaction_1.MongoTransaction.sessionFor(options?.transaction)
37
- : undefined;
38
- const hasFields = options?.fieldsToExclude && options.fieldsToExclude.length > 0;
39
- const projection = hasFields
40
- ? {}
41
- : undefined;
42
- if (hasFields) {
43
- options.fieldsToExclude.forEach((field) => {
44
- projection[field] = 0;
45
- });
46
- }
47
- const findOptions = session
48
- ? { ...(projection ? { projection } : {}), session }
49
- : projection
50
- ? { projection }
51
- : undefined;
52
35
  let order = { _id: -1 };
53
36
  if (options?.sort?.hasOrder()) {
54
37
  order = {
@@ -59,14 +42,17 @@ class MongoRepository {
59
42
  : -1,
60
43
  };
61
44
  }
45
+ const session = MongoTransaction_1.MongoTransaction.sessionFor(options?.transaction);
62
46
  const documents = await collection
63
- .find(filter, findOptions)
47
+ .find(filter, session ? { session } : undefined)
64
48
  .sort(order)
65
49
  .toArray();
66
- return documents.map((document) => this.aggregateRootClass.fromPrimitives({
67
- ...document,
68
- id: document._id.toString(),
69
- }));
50
+ return documents.map((document) => {
51
+ const { _id, ...primitives } = document;
52
+ const entity = this.aggregateRootClass.fromPrimitives(primitives);
53
+ entity.assignId(_id.toString());
54
+ return entity;
55
+ });
70
56
  }
71
57
  /** Upserts an aggregate by delegating to persist with its id. */
72
58
  async upsert(entity, transaction) {
@@ -84,9 +70,10 @@ class MongoRepository {
84
70
  }, transaction);
85
71
  }
86
72
  /** Lists entities by criteria and returns a paginated response. */
87
- async list(criteria, fieldsToExclude = [], transaction) {
88
- const documents = await this.searchByCriteria(criteria, fieldsToExclude, transaction);
89
- return this.paginate(documents, transaction);
73
+ async list(criteria, transaction) {
74
+ const query = this.criteriaConverter.convert(criteria);
75
+ const documents = await this.searchByCriteria(query, transaction);
76
+ return this.paginate(documents, query, criteria, transaction);
90
77
  }
91
78
  /**
92
79
  * Deletes documents from the database based on a filter and optional options.
@@ -123,50 +110,37 @@ class MongoRepository {
123
110
  .db()
124
111
  .collection(this.collectionName());
125
112
  }
126
- async searchByCriteria(criteria, fieldsToExclude = [], transaction) {
127
- this.criteria = criteria;
128
- this.query = this.criteriaConverter.convert(criteria);
113
+ async searchByCriteria(query, transaction) {
129
114
  const collection = await this.collection();
130
115
  const session = MongoTransaction_1.MongoTransaction.sessionFor(transaction);
131
- if (fieldsToExclude.length === 0) {
132
- const results = await collection
133
- .find(this.query.filter, session ? { session } : {})
134
- .sort(this.query.sort)
135
- .skip(this.query.skip)
136
- .limit(this.query.limit)
137
- .toArray();
138
- return results.map(({ _id, ...rest }) => rest);
139
- }
140
- const projection = {};
141
- fieldsToExclude.forEach((field) => {
142
- projection[field] = 0;
143
- });
144
116
  const results = await collection
145
- .find(this.query.filter, session ? { projection, session } : { projection })
146
- .sort(this.query.sort)
147
- .skip(this.query.skip)
148
- .limit(this.query.limit)
117
+ .find(query.filter, session ? { session } : undefined)
118
+ .sort(query.sort)
119
+ .skip(query.skip)
120
+ .limit(query.limit)
149
121
  .toArray();
150
- return results.map(({ _id, ...rest }) => rest);
122
+ return results.map(({ _id, ...rest }) => {
123
+ const entity = this.aggregateRootClass.fromPrimitives(rest);
124
+ entity.assignId(_id.toString());
125
+ return entity;
126
+ });
151
127
  }
152
- async paginate(documents, transaction) {
128
+ async paginate(documents, query, criteria, transaction) {
153
129
  const collection = await this.collection();
154
130
  const session = MongoTransaction_1.MongoTransaction.sessionFor(transaction);
155
- const count = session
156
- ? await collection.countDocuments(this.query.filter, { session })
157
- : await collection.countDocuments(this.query.filter);
158
- const limit = this.criteria?.limit || 10;
159
- const currentPage = this.criteria?.currentPage || 1;
160
- const hasNextPage = currentPage * limit < count;
131
+ const count = await collection.countDocuments(query.filter, session ? { session } : undefined);
132
+ const limit = criteria.limit;
133
+ const currentPage = criteria.currentPage;
134
+ const hasNextPage = limit > 0 && currentPage * limit < count;
161
135
  if (documents.length === 0) {
162
136
  return {
163
137
  nextPag: null,
164
- count: 0,
138
+ count,
165
139
  results: [],
166
140
  };
167
141
  }
168
142
  return {
169
- nextPag: hasNextPage ? Number(this.criteria.currentPage) + 1 : null,
143
+ nextPag: hasNextPage ? Number(criteria.currentPage) + 1 : null,
170
144
  count: count,
171
145
  results: documents,
172
146
  };
@@ -1,7 +1,4 @@
1
1
  export class AggregateRoot {
2
- constructor(id) {
3
- this.aggregateId = id;
4
- }
5
2
  getId() {
6
3
  return this.aggregateId;
7
4
  }
@@ -1,14 +1,16 @@
1
1
  export class Criteria {
2
- constructor(filters, order, limit, offset) {
2
+ constructor(filters, order, limit = 10, currentPage = 1) {
3
+ if (!Number.isInteger(limit) || limit <= 0) {
4
+ throw new Error("CRITERIA_LIMIT_MUST_BE_A_POSITIVE_INTEGER");
5
+ }
6
+ if (!Number.isInteger(currentPage) || currentPage <= 0) {
7
+ throw new Error("CRITERIA_PAGE_MUST_BE_A_POSITIVE_INTEGER");
8
+ }
3
9
  this.filters = filters;
4
10
  this.order = order;
5
11
  this.limit = limit;
6
- this.currentPage = offset;
7
- if (offset && limit) {
8
- this.offset = offset > 0 ? (offset - 1) * limit : 0;
9
- }
10
- else
11
- this.offset = undefined;
12
+ this.currentPage = currentPage;
13
+ this.offset = (currentPage - 1) * limit;
12
14
  }
13
15
  hasFilters() {
14
16
  return this.filters.filters.length > 0;
@@ -24,8 +24,8 @@ export class MongoCriteriaConverter {
24
24
  sort: criteria.order.hasOrder()
25
25
  ? this.generateSort(criteria.order)
26
26
  : { _id: -1 },
27
- skip: criteria.offset || 0,
28
- limit: criteria.limit || 0,
27
+ skip: criteria.offset,
28
+ limit: criteria.limit,
29
29
  };
30
30
  }
31
31
  generateFilter(filters) {
@@ -17,10 +17,10 @@ export class MongoRepository {
17
17
  if (!result) {
18
18
  return null;
19
19
  }
20
- return this.aggregateRootClass.fromPrimitives({
21
- ...result,
22
- id: result._id.toString(),
23
- });
20
+ const { _id, ...primitives } = result;
21
+ const entity = this.aggregateRootClass.fromPrimitives(primitives);
22
+ entity.assignId(_id.toString());
23
+ return entity;
24
24
  }
25
25
  /**
26
26
  *
@@ -29,23 +29,6 @@ export class MongoRepository {
29
29
  */
30
30
  async many(filter, options) {
31
31
  const collection = await this.collection();
32
- const session = options?.transaction
33
- ? MongoTransaction.sessionFor(options?.transaction)
34
- : undefined;
35
- const hasFields = options?.fieldsToExclude && options.fieldsToExclude.length > 0;
36
- const projection = hasFields
37
- ? {}
38
- : undefined;
39
- if (hasFields) {
40
- options.fieldsToExclude.forEach((field) => {
41
- projection[field] = 0;
42
- });
43
- }
44
- const findOptions = session
45
- ? { ...(projection ? { projection } : {}), session }
46
- : projection
47
- ? { projection }
48
- : undefined;
49
32
  let order = { _id: -1 };
50
33
  if (options?.sort?.hasOrder()) {
51
34
  order = {
@@ -56,14 +39,17 @@ export class MongoRepository {
56
39
  : -1,
57
40
  };
58
41
  }
42
+ const session = MongoTransaction.sessionFor(options?.transaction);
59
43
  const documents = await collection
60
- .find(filter, findOptions)
44
+ .find(filter, session ? { session } : undefined)
61
45
  .sort(order)
62
46
  .toArray();
63
- return documents.map((document) => this.aggregateRootClass.fromPrimitives({
64
- ...document,
65
- id: document._id.toString(),
66
- }));
47
+ return documents.map((document) => {
48
+ const { _id, ...primitives } = document;
49
+ const entity = this.aggregateRootClass.fromPrimitives(primitives);
50
+ entity.assignId(_id.toString());
51
+ return entity;
52
+ });
67
53
  }
68
54
  /** Upserts an aggregate by delegating to persist with its id. */
69
55
  async upsert(entity, transaction) {
@@ -81,9 +67,10 @@ export class MongoRepository {
81
67
  }, transaction);
82
68
  }
83
69
  /** Lists entities by criteria and returns a paginated response. */
84
- async list(criteria, fieldsToExclude = [], transaction) {
85
- const documents = await this.searchByCriteria(criteria, fieldsToExclude, transaction);
86
- return this.paginate(documents, transaction);
70
+ async list(criteria, transaction) {
71
+ const query = this.criteriaConverter.convert(criteria);
72
+ const documents = await this.searchByCriteria(query, transaction);
73
+ return this.paginate(documents, query, criteria, transaction);
87
74
  }
88
75
  /**
89
76
  * Deletes documents from the database based on a filter and optional options.
@@ -120,50 +107,37 @@ export class MongoRepository {
120
107
  .db()
121
108
  .collection(this.collectionName());
122
109
  }
123
- async searchByCriteria(criteria, fieldsToExclude = [], transaction) {
124
- this.criteria = criteria;
125
- this.query = this.criteriaConverter.convert(criteria);
110
+ async searchByCriteria(query, transaction) {
126
111
  const collection = await this.collection();
127
112
  const session = MongoTransaction.sessionFor(transaction);
128
- if (fieldsToExclude.length === 0) {
129
- const results = await collection
130
- .find(this.query.filter, session ? { session } : {})
131
- .sort(this.query.sort)
132
- .skip(this.query.skip)
133
- .limit(this.query.limit)
134
- .toArray();
135
- return results.map(({ _id, ...rest }) => rest);
136
- }
137
- const projection = {};
138
- fieldsToExclude.forEach((field) => {
139
- projection[field] = 0;
140
- });
141
113
  const results = await collection
142
- .find(this.query.filter, session ? { projection, session } : { projection })
143
- .sort(this.query.sort)
144
- .skip(this.query.skip)
145
- .limit(this.query.limit)
114
+ .find(query.filter, session ? { session } : undefined)
115
+ .sort(query.sort)
116
+ .skip(query.skip)
117
+ .limit(query.limit)
146
118
  .toArray();
147
- return results.map(({ _id, ...rest }) => rest);
119
+ return results.map(({ _id, ...rest }) => {
120
+ const entity = this.aggregateRootClass.fromPrimitives(rest);
121
+ entity.assignId(_id.toString());
122
+ return entity;
123
+ });
148
124
  }
149
- async paginate(documents, transaction) {
125
+ async paginate(documents, query, criteria, transaction) {
150
126
  const collection = await this.collection();
151
127
  const session = MongoTransaction.sessionFor(transaction);
152
- const count = session
153
- ? await collection.countDocuments(this.query.filter, { session })
154
- : await collection.countDocuments(this.query.filter);
155
- const limit = this.criteria?.limit || 10;
156
- const currentPage = this.criteria?.currentPage || 1;
157
- const hasNextPage = currentPage * limit < count;
128
+ const count = await collection.countDocuments(query.filter, session ? { session } : undefined);
129
+ const limit = criteria.limit;
130
+ const currentPage = criteria.currentPage;
131
+ const hasNextPage = limit > 0 && currentPage * limit < count;
158
132
  if (documents.length === 0) {
159
133
  return {
160
134
  nextPag: null,
161
- count: 0,
135
+ count,
162
136
  results: [],
163
137
  };
164
138
  }
165
139
  return {
166
- nextPag: hasNextPage ? Number(this.criteria.currentPage) + 1 : null,
140
+ nextPag: hasNextPage ? Number(criteria.currentPage) + 1 : null,
167
141
  count: count,
168
142
  results: documents,
169
143
  };
@@ -1,12 +1,9 @@
1
1
  export declare abstract class AggregateRoot {
2
2
  private aggregateId?;
3
- protected constructor(id?: string);
4
3
  getId(): string | undefined;
5
4
  assignId(mongoId: string): void;
6
5
  abstract toPrimitives(): any;
7
6
  }
8
7
  export type AggregateRootClass<T extends AggregateRoot> = {
9
- fromPrimitives(data: Record<string, unknown> & {
10
- readonly id: string;
11
- }): T;
8
+ fromPrimitives(data: Record<string, unknown>): T;
12
9
  };
@@ -3,9 +3,9 @@ import { Order } from "./Order";
3
3
  export declare class Criteria {
4
4
  readonly filters: Filters;
5
5
  readonly order: Order;
6
- readonly limit?: number;
7
- readonly offset?: number;
8
- readonly currentPage?: number;
9
- constructor(filters: Filters, order: Order, limit?: number, offset?: number);
6
+ readonly limit: number;
7
+ readonly offset: number;
8
+ readonly currentPage: number;
9
+ constructor(filters: Filters, order: Order, limit?: number, currentPage?: number);
10
10
  hasFilters(): boolean;
11
11
  }
@@ -1,16 +1,14 @@
1
- import { Criteria, Paginate } from "../criteria";
1
+ import { Criteria, Order, Paginate } from "../criteria";
2
2
  import { AggregateRoot } from "../AggregateRoot";
3
3
  import { DeleteOptions } from "mongodb";
4
4
  import { MongoTransaction } from "./MongoTransaction";
5
- import { Order } from "../criteria";
6
5
  export interface IRepository<T extends AggregateRoot> {
7
6
  many(filter: object, options?: {
8
7
  transaction?: MongoTransaction;
9
- fieldsToExclude?: string[];
10
8
  sort?: Order;
11
9
  }): Promise<T[]>;
12
10
  one(filter: object, transaction?: MongoTransaction): Promise<T | null>;
13
- list<D>(criteria: Criteria, fieldsToExclude?: string[], transaction?: MongoTransaction): Promise<Paginate<D>>;
11
+ list(criteria: Criteria, transaction?: MongoTransaction): Promise<Paginate<T>>;
14
12
  upsert(entity: T, transaction?: MongoTransaction): Promise<void>;
15
13
  delete(filter: object, options?: DeleteOptions, transaction?: MongoTransaction): Promise<void>;
16
14
  }
@@ -6,8 +6,6 @@ export declare abstract class MongoRepository<T extends AggregateRoot> {
6
6
  private readonly aggregateRootClass;
7
7
  private static indexRegistry;
8
8
  private criteriaConverter;
9
- private query;
10
- private criteria;
11
9
  protected constructor(aggregateRootClass: AggregateRootClass<T>);
12
10
  abstract collectionName(): string;
13
11
  /** Finds a single entity and hydrates it via the aggregate's fromPrimitives. */
@@ -19,13 +17,12 @@ export declare abstract class MongoRepository<T extends AggregateRoot> {
19
17
  */
20
18
  many(filter: object, options?: {
21
19
  transaction?: MongoTransaction;
22
- fieldsToExclude?: string[];
23
20
  sort?: Order;
24
21
  }): Promise<T[]>;
25
22
  /** Upserts an aggregate by delegating to persist with its id. */
26
23
  upsert(entity: T, transaction?: MongoTransaction): Promise<void>;
27
24
  /** Lists entities by criteria and returns a paginated response. */
28
- list<D>(criteria: Criteria, fieldsToExclude?: string[], transaction?: MongoTransaction): Promise<Paginate<D>>;
25
+ list(criteria: Criteria, transaction?: MongoTransaction): Promise<Paginate<T>>;
29
26
  /**
30
27
  * Deletes documents from the database based on a filter and optional options.
31
28
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@abejarano/ts-mongodb-criteria",
3
3
  "author": "angel bejarano / angel.bejarano@jaspesoft.com",
4
- "version": "1.11.2",
4
+ "version": "1.11.3",
5
5
  "description": "Patrón Criteria para consultas MongoDB en TypeScript",
6
6
  "main": "dist/cjs/index.js",
7
7
  "module": "dist/esm/index.js",