@abejarano/ts-mongodb-criteria 1.11.0 → 1.11.1
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 +11 -8
- package/dist/cjs/AggregateRoot.js +11 -2
- package/dist/cjs/mongo/MongoRepository.js +30 -19
- package/dist/cjs/types/index.js +2 -0
- package/dist/esm/AggregateRoot.js +11 -2
- package/dist/esm/mongo/MongoRepository.js +30 -19
- package/dist/esm/types/index.js +1 -0
- package/dist/types/AggregateRoot.d.ts +7 -3
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mongo/IRepository.d.ts +6 -1
- package/dist/types/mongo/MongoCriteriaConverter.d.ts +1 -20
- package/dist/types/mongo/MongoRepository.d.ts +6 -2
- package/dist/types/types/index.d.ts +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://www.mongodb.com/)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
|
-
[](#testing)
|
|
10
10
|
|
|
11
11
|
> A robust, type-safe implementation of the **Criteria Pattern** for MongoDB queries in TypeScript. Build complex database queries dynamically with a fluent, composable API designed following Domain-Driven Design (DDD) and Clean Architecture principles.
|
|
12
12
|
|
|
@@ -182,7 +182,7 @@ Use the `collection` argument inside `ensureIndexes` to avoid recursion.
|
|
|
182
182
|
MongoRepository provides ready-to-use public methods for repositories that extend it:
|
|
183
183
|
|
|
184
184
|
- `list(criteria, fieldsToExclude?)` for paginated queries
|
|
185
|
-
- `many(filter,
|
|
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
|
|
@@ -199,8 +199,8 @@ export interface IUserRepository extends IRepository<User> {
|
|
|
199
199
|
}
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
-
The goal of `IRepository` is to prevent signature drift
|
|
203
|
-
|
|
202
|
+
The goal of `IRepository` is to prevent signature drift between your app's
|
|
203
|
+
interfaces and the library's repository return types.
|
|
204
204
|
|
|
205
205
|
## Atomic Transactions
|
|
206
206
|
|
|
@@ -218,7 +218,10 @@ await MongoTransaction.run(async (tx) => {
|
|
|
218
218
|
|
|
219
219
|
// Reads can observe writes made earlier in this transaction.
|
|
220
220
|
const persistedUser = await userRepository.one({ id: user.getId() }, tx)
|
|
221
|
-
const activeUsers = await userRepository.many(
|
|
221
|
+
const activeUsers = await userRepository.many(
|
|
222
|
+
{ status: "active" },
|
|
223
|
+
{ transaction: tx, sort: { name: 1 } }
|
|
224
|
+
)
|
|
222
225
|
})
|
|
223
226
|
```
|
|
224
227
|
|
|
@@ -237,8 +240,8 @@ async deactivateUser(id: string, tx: MongoTransaction): Promise<void> {
|
|
|
237
240
|
|
|
238
241
|
MongoDB transactions require a replica set or a sharded cluster; standalone
|
|
239
242
|
MongoDB instances do not support them. This initial API applies the transaction
|
|
240
|
-
context to `upsert`, `delete`, protected `updateOne`, `one`, and `
|
|
241
|
-
Pass `tx` as the third argument to `list` after `fieldsToExclude`, and
|
|
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`.
|
|
242
245
|
|
|
243
246
|
**Your First Query in 30 Seconds:**
|
|
244
247
|
|
|
@@ -385,7 +388,7 @@ const criteria = new Criteria(Filters.fromValues(filters), Order.none())
|
|
|
385
388
|
|
|
386
389
|
## 🧪 Testing
|
|
387
390
|
|
|
388
|
-
The library includes comprehensive test coverage (
|
|
391
|
+
The library includes comprehensive test coverage (46/46 tests passing).
|
|
389
392
|
|
|
390
393
|
```bash
|
|
391
394
|
# Run all tests
|
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AggregateRoot = void 0;
|
|
4
4
|
class AggregateRoot {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
constructor(id) {
|
|
6
|
+
this.aggregateId = id;
|
|
7
|
+
}
|
|
8
|
+
getId() {
|
|
9
|
+
return this.aggregateId;
|
|
10
|
+
}
|
|
11
|
+
assignId(mongoId) {
|
|
12
|
+
if (this.aggregateId !== undefined) {
|
|
13
|
+
throw new Error("AGGREGATE_ID_ALREADY_ASSIGNED");
|
|
14
|
+
}
|
|
15
|
+
this.aggregateId = mongoId;
|
|
7
16
|
}
|
|
8
17
|
}
|
|
9
18
|
exports.AggregateRoot = AggregateRoot;
|
|
@@ -25,11 +25,26 @@ class MongoRepository {
|
|
|
25
25
|
id: result._id.toString(),
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
-
async many(filter,
|
|
28
|
+
async many(filter, options) {
|
|
29
29
|
const collection = await this.collection();
|
|
30
|
-
const session =
|
|
30
|
+
const session = options?.transaction
|
|
31
|
+
? MongoTransaction_1.MongoTransaction.sessionFor(options?.transaction)
|
|
32
|
+
: undefined;
|
|
33
|
+
const hasFields = options?.fields && options.fields.length > 0;
|
|
34
|
+
const projection = hasFields ? {} : undefined;
|
|
35
|
+
if (hasFields) {
|
|
36
|
+
options.fields.forEach((field) => {
|
|
37
|
+
projection[field] = 1;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const findOptions = session
|
|
41
|
+
? { ...(projection ? { projection } : {}), session }
|
|
42
|
+
: projection
|
|
43
|
+
? { projection }
|
|
44
|
+
: undefined;
|
|
31
45
|
const documents = await collection
|
|
32
|
-
.find(filter,
|
|
46
|
+
.find(filter, findOptions)
|
|
47
|
+
.sort(options?.sort ? options?.sort : { _id: -1 })
|
|
33
48
|
.toArray();
|
|
34
49
|
return documents.map((document) => this.aggregateRootClass.fromPrimitives({
|
|
35
50
|
...document,
|
|
@@ -38,7 +53,18 @@ class MongoRepository {
|
|
|
38
53
|
}
|
|
39
54
|
/** Upserts an aggregate by delegating to persist with its id. */
|
|
40
55
|
async upsert(entity, transaction) {
|
|
41
|
-
|
|
56
|
+
const primitiveResult = entity.toPrimitives();
|
|
57
|
+
const primitives = await Promise.resolve(primitiveResult);
|
|
58
|
+
const currentId = entity.getId();
|
|
59
|
+
const mongoId = currentId === undefined ? new mongodb_1.ObjectId() : new mongodb_1.ObjectId(currentId);
|
|
60
|
+
if (currentId === undefined) {
|
|
61
|
+
entity.assignId(mongoId.toString());
|
|
62
|
+
}
|
|
63
|
+
await this.updateOne({ _id: mongoId }, {
|
|
64
|
+
$set: {
|
|
65
|
+
...primitives,
|
|
66
|
+
},
|
|
67
|
+
}, transaction);
|
|
42
68
|
}
|
|
43
69
|
/** Lists entities by criteria and returns a paginated response. */
|
|
44
70
|
async list(criteria, fieldsToExclude = [], transaction) {
|
|
@@ -79,21 +105,6 @@ class MongoRepository {
|
|
|
79
105
|
.db()
|
|
80
106
|
.collection(this.collectionName());
|
|
81
107
|
}
|
|
82
|
-
async persist(id, aggregateRoot, transaction) {
|
|
83
|
-
let primitives;
|
|
84
|
-
if (aggregateRoot.toPrimitives() instanceof Promise) {
|
|
85
|
-
primitives = await aggregateRoot.toPrimitives();
|
|
86
|
-
}
|
|
87
|
-
else {
|
|
88
|
-
primitives = aggregateRoot.toPrimitives();
|
|
89
|
-
}
|
|
90
|
-
await this.updateOne({ _id: new mongodb_1.ObjectId(id) }, {
|
|
91
|
-
$set: {
|
|
92
|
-
...primitives,
|
|
93
|
-
id: id,
|
|
94
|
-
},
|
|
95
|
-
}, transaction);
|
|
96
|
-
}
|
|
97
108
|
async searchByCriteria(criteria, fieldsToExclude = [], transaction) {
|
|
98
109
|
this.criteria = criteria;
|
|
99
110
|
this.query = this.criteriaConverter.convert(criteria);
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export class AggregateRoot {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
constructor(id) {
|
|
3
|
+
this.aggregateId = id;
|
|
4
|
+
}
|
|
5
|
+
getId() {
|
|
6
|
+
return this.aggregateId;
|
|
7
|
+
}
|
|
8
|
+
assignId(mongoId) {
|
|
9
|
+
if (this.aggregateId !== undefined) {
|
|
10
|
+
throw new Error("AGGREGATE_ID_ALREADY_ASSIGNED");
|
|
11
|
+
}
|
|
12
|
+
this.aggregateId = mongoId;
|
|
4
13
|
}
|
|
5
14
|
}
|
|
@@ -22,11 +22,26 @@ export class MongoRepository {
|
|
|
22
22
|
id: result._id.toString(),
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
-
async many(filter,
|
|
25
|
+
async many(filter, options) {
|
|
26
26
|
const collection = await this.collection();
|
|
27
|
-
const session =
|
|
27
|
+
const session = options?.transaction
|
|
28
|
+
? MongoTransaction.sessionFor(options?.transaction)
|
|
29
|
+
: undefined;
|
|
30
|
+
const hasFields = options?.fields && options.fields.length > 0;
|
|
31
|
+
const projection = hasFields ? {} : undefined;
|
|
32
|
+
if (hasFields) {
|
|
33
|
+
options.fields.forEach((field) => {
|
|
34
|
+
projection[field] = 1;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const findOptions = session
|
|
38
|
+
? { ...(projection ? { projection } : {}), session }
|
|
39
|
+
: projection
|
|
40
|
+
? { projection }
|
|
41
|
+
: undefined;
|
|
28
42
|
const documents = await collection
|
|
29
|
-
.find(filter,
|
|
43
|
+
.find(filter, findOptions)
|
|
44
|
+
.sort(options?.sort ? options?.sort : { _id: -1 })
|
|
30
45
|
.toArray();
|
|
31
46
|
return documents.map((document) => this.aggregateRootClass.fromPrimitives({
|
|
32
47
|
...document,
|
|
@@ -35,7 +50,18 @@ export class MongoRepository {
|
|
|
35
50
|
}
|
|
36
51
|
/** Upserts an aggregate by delegating to persist with its id. */
|
|
37
52
|
async upsert(entity, transaction) {
|
|
38
|
-
|
|
53
|
+
const primitiveResult = entity.toPrimitives();
|
|
54
|
+
const primitives = await Promise.resolve(primitiveResult);
|
|
55
|
+
const currentId = entity.getId();
|
|
56
|
+
const mongoId = currentId === undefined ? new ObjectId() : new ObjectId(currentId);
|
|
57
|
+
if (currentId === undefined) {
|
|
58
|
+
entity.assignId(mongoId.toString());
|
|
59
|
+
}
|
|
60
|
+
await this.updateOne({ _id: mongoId }, {
|
|
61
|
+
$set: {
|
|
62
|
+
...primitives,
|
|
63
|
+
},
|
|
64
|
+
}, transaction);
|
|
39
65
|
}
|
|
40
66
|
/** Lists entities by criteria and returns a paginated response. */
|
|
41
67
|
async list(criteria, fieldsToExclude = [], transaction) {
|
|
@@ -76,21 +102,6 @@ export class MongoRepository {
|
|
|
76
102
|
.db()
|
|
77
103
|
.collection(this.collectionName());
|
|
78
104
|
}
|
|
79
|
-
async persist(id, aggregateRoot, transaction) {
|
|
80
|
-
let primitives;
|
|
81
|
-
if (aggregateRoot.toPrimitives() instanceof Promise) {
|
|
82
|
-
primitives = await aggregateRoot.toPrimitives();
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
primitives = aggregateRoot.toPrimitives();
|
|
86
|
-
}
|
|
87
|
-
await this.updateOne({ _id: new ObjectId(id) }, {
|
|
88
|
-
$set: {
|
|
89
|
-
...primitives,
|
|
90
|
-
id: id,
|
|
91
|
-
},
|
|
92
|
-
}, transaction);
|
|
93
|
-
}
|
|
94
105
|
async searchByCriteria(criteria, fieldsToExclude = [], transaction) {
|
|
95
106
|
this.criteria = criteria;
|
|
96
107
|
this.query = this.criteriaConverter.convert(criteria);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export declare abstract class AggregateRoot {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
private aggregateId?;
|
|
3
|
+
protected constructor(id?: string);
|
|
4
|
+
getId(): string | undefined;
|
|
5
|
+
assignId(mongoId: string): void;
|
|
4
6
|
abstract toPrimitives(): any;
|
|
5
7
|
}
|
|
6
8
|
export type AggregateRootClass<T extends AggregateRoot> = {
|
|
7
|
-
fromPrimitives(data:
|
|
9
|
+
fromPrimitives(data: Record<string, unknown> & {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
}): T;
|
|
8
12
|
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,8 +2,13 @@ import { Criteria, Paginate } from "../criteria";
|
|
|
2
2
|
import { AggregateRoot } from "../AggregateRoot";
|
|
3
3
|
import { DeleteOptions } from "mongodb";
|
|
4
4
|
import { MongoTransaction } from "./MongoTransaction";
|
|
5
|
+
import { MongoSort } from "../types";
|
|
5
6
|
export interface IRepository<T extends AggregateRoot> {
|
|
6
|
-
many(filter: object,
|
|
7
|
+
many(filter: object, options?: {
|
|
8
|
+
transaction?: MongoTransaction;
|
|
9
|
+
fields?: string[];
|
|
10
|
+
sort?: MongoSort;
|
|
11
|
+
}): Promise<T[]>;
|
|
7
12
|
one(filter: object, transaction?: MongoTransaction): Promise<T | null>;
|
|
8
13
|
list<D>(criteria: Criteria, fieldsToExclude?: string[], transaction?: MongoTransaction): Promise<Paginate<D>>;
|
|
9
14
|
upsert(entity: T, transaction?: MongoTransaction): Promise<void>;
|
|
@@ -1,23 +1,5 @@
|
|
|
1
1
|
import { Criteria, Filters, Order } from "../criteria";
|
|
2
|
-
|
|
3
|
-
type MongoFilterValue = boolean | string | number | Date;
|
|
4
|
-
type MongoFilterArrayValue = MongoFilterValue[];
|
|
5
|
-
type MongoFilterOperation = {
|
|
6
|
-
[operator in MongoFilterOperator]?: MongoFilterValue | MongoFilterArrayValue;
|
|
7
|
-
};
|
|
8
|
-
type MongoFilter = {
|
|
9
|
-
[field: string]: MongoFilterOperation;
|
|
10
|
-
} | {
|
|
11
|
-
[field: string]: {
|
|
12
|
-
$not: MongoFilterOperation;
|
|
13
|
-
};
|
|
14
|
-
} | {
|
|
15
|
-
$or: any[];
|
|
16
|
-
};
|
|
17
|
-
type MongoDirection = 1 | -1;
|
|
18
|
-
type MongoSort = {
|
|
19
|
-
[field: string]: MongoDirection;
|
|
20
|
-
};
|
|
2
|
+
import { MongoFilter, MongoSort } from "../types";
|
|
21
3
|
export interface MongoQuery {
|
|
22
4
|
filter: MongoFilter;
|
|
23
5
|
sort: MongoSort;
|
|
@@ -43,4 +25,3 @@ export declare class MongoCriteriaConverter {
|
|
|
43
25
|
private orFilter;
|
|
44
26
|
private betweenFilter;
|
|
45
27
|
}
|
|
46
|
-
export {};
|
|
@@ -2,6 +2,7 @@ import { MongoTransaction } from "./MongoTransaction";
|
|
|
2
2
|
import { Criteria, Paginate } from "../criteria";
|
|
3
3
|
import { AggregateRoot, AggregateRootClass } from "../AggregateRoot";
|
|
4
4
|
import { Collection, DeleteOptions, Document, UpdateFilter } from "mongodb";
|
|
5
|
+
import { MongoSort } from "../types";
|
|
5
6
|
export declare abstract class MongoRepository<T extends AggregateRoot> {
|
|
6
7
|
private readonly aggregateRootClass;
|
|
7
8
|
private static indexRegistry;
|
|
@@ -12,7 +13,11 @@ export declare abstract class MongoRepository<T extends AggregateRoot> {
|
|
|
12
13
|
abstract collectionName(): string;
|
|
13
14
|
/** Finds a single entity and hydrates it via the aggregate's fromPrimitives. */
|
|
14
15
|
one(filter: object, transaction?: MongoTransaction): Promise<T | null>;
|
|
15
|
-
many(filter: object,
|
|
16
|
+
many(filter: object, options?: {
|
|
17
|
+
transaction?: MongoTransaction;
|
|
18
|
+
fields?: string[];
|
|
19
|
+
sort?: MongoSort;
|
|
20
|
+
}): Promise<T[]>;
|
|
16
21
|
/** Upserts an aggregate by delegating to persist with its id. */
|
|
17
22
|
upsert(entity: T, transaction?: MongoTransaction): Promise<void>;
|
|
18
23
|
/** Lists entities by criteria and returns a paginated response. */
|
|
@@ -30,7 +35,6 @@ export declare abstract class MongoRepository<T extends AggregateRoot> {
|
|
|
30
35
|
protected collection<U extends Document>(): Promise<Collection<U>>;
|
|
31
36
|
protected updateOne(filter: object, update: Document[] | UpdateFilter<any>, transaction?: MongoTransaction): Promise<void>;
|
|
32
37
|
private collectionRaw;
|
|
33
|
-
private persist;
|
|
34
38
|
private searchByCriteria;
|
|
35
39
|
private paginate;
|
|
36
40
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type MongoFilterOperator = "$eq" | "$ne" | "$gt" | "$lt" | "$regex" | "$lte" | "$gte" | "$or" | "$in" | "$nin";
|
|
2
|
+
export type MongoFilterBetween = {
|
|
3
|
+
[p: string]: {
|
|
4
|
+
$gte: MongoFilterValue;
|
|
5
|
+
$lte: MongoFilterValue;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
export type MongoFilterValue = boolean | string | number | Date;
|
|
9
|
+
export type MongoFilterArrayValue = MongoFilterValue[];
|
|
10
|
+
export type MongoFilterOperation = {
|
|
11
|
+
[operator in MongoFilterOperator]?: MongoFilterValue | MongoFilterArrayValue;
|
|
12
|
+
};
|
|
13
|
+
export type MongoFilter = {
|
|
14
|
+
[field: string]: MongoFilterOperation;
|
|
15
|
+
} | {
|
|
16
|
+
[field: string]: {
|
|
17
|
+
$not: MongoFilterOperation;
|
|
18
|
+
};
|
|
19
|
+
} | {
|
|
20
|
+
$or: any[];
|
|
21
|
+
};
|
|
22
|
+
export type MongoDirection = 1 | -1;
|
|
23
|
+
export type MongoSort = {
|
|
24
|
+
[field: string]: MongoDirection;
|
|
25
|
+
};
|
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.
|
|
4
|
+
"version": "1.11.1",
|
|
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",
|