@abejarano/ts-mongodb-criteria 1.8.5 → 1.9.0
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 +34 -0
- package/dist/cjs/criteria/Criteria.js +5 -4
- package/dist/cjs/mongo/MongoRepository.js +11 -10
- package/dist/cjs/mongo/MongoTransaction.js +32 -0
- package/dist/cjs/mongo/index.js +1 -0
- package/dist/esm/criteria/Criteria.js +5 -4
- package/dist/esm/mongo/MongoRepository.js +11 -10
- package/dist/esm/mongo/MongoTransaction.js +28 -0
- package/dist/esm/mongo/index.js +1 -0
- package/dist/types/mongo/IRepository.d.ts +3 -2
- package/dist/types/mongo/MongoRepository.d.ts +4 -3
- package/dist/types/mongo/MongoTransaction.d.ts +13 -0
- package/dist/types/mongo/index.d.ts +1 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -201,6 +201,40 @@ export interface IUserRepository extends IRepository<User> {
|
|
|
201
201
|
The goal of `IRepository` is to prevent signature drift (e.g. `upsert` returning
|
|
202
202
|
`void` in your app while the base repository returns `ObjectId | null`).
|
|
203
203
|
|
|
204
|
+
## Atomic Transactions
|
|
205
|
+
|
|
206
|
+
`MongoTransaction.run` groups writes from one or more repositories into a
|
|
207
|
+
single MongoDB transaction. Pass the `tx` context to every write that must be
|
|
208
|
+
committed or rolled back together:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { MongoTransaction } from "@abejarano/ts-mongodb-criteria"
|
|
212
|
+
|
|
213
|
+
await MongoTransaction.run(async (tx) => {
|
|
214
|
+
await userRepository.upsert(user, tx)
|
|
215
|
+
await auditRepository.upsert(auditEntry, tx)
|
|
216
|
+
await sessionRepository.delete({ userId: user.getId() }, undefined, tx)
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
If any operation in the callback fails, MongoDB rolls back all the writes and
|
|
221
|
+
the error is propagated to the caller. The MongoDB driver handles retryable
|
|
222
|
+
transaction errors through `withTransaction`.
|
|
223
|
+
|
|
224
|
+
Custom repositories can include their protected atomic updates in the same
|
|
225
|
+
transaction by accepting and forwarding `MongoTransaction`:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
async deactivateUser(id: string, tx: MongoTransaction): Promise<void> {
|
|
229
|
+
await this.updateOne({ id }, { $set: { active: false } }, tx)
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
MongoDB transactions require a replica set or a sharded cluster; standalone
|
|
234
|
+
MongoDB instances do not support them. This initial API applies the transaction
|
|
235
|
+
context only to `upsert`, `delete`, and protected `updateOne`; `one` and `list`
|
|
236
|
+
remain regular reads.
|
|
237
|
+
|
|
204
238
|
**Your First Query in 30 Seconds:**
|
|
205
239
|
|
|
206
240
|
```typescript
|
|
@@ -7,10 +7,11 @@ class Criteria {
|
|
|
7
7
|
this.order = order;
|
|
8
8
|
this.limit = limit;
|
|
9
9
|
this.currentPage = offset;
|
|
10
|
-
|
|
11
|
-
offset
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
if (offset && limit) {
|
|
11
|
+
this.offset = offset > 0 ? (offset - 1) * limit : 0;
|
|
12
|
+
}
|
|
13
|
+
else
|
|
14
|
+
this.offset = undefined;
|
|
14
15
|
}
|
|
15
16
|
hasFilters() {
|
|
16
17
|
return this.filters.filters.length > 0;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.MongoRepository = void 0;
|
|
4
4
|
const MongoCriteriaConverter_1 = require("./MongoCriteriaConverter");
|
|
5
5
|
const MongoClientFactory_1 = require("./MongoClientFactory");
|
|
6
|
+
const MongoTransaction_1 = require("./MongoTransaction");
|
|
6
7
|
const mongodb_1 = require("mongodb");
|
|
7
8
|
class MongoRepository {
|
|
8
9
|
constructor(aggregateRootClass) {
|
|
@@ -22,8 +23,8 @@ class MongoRepository {
|
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
/** Upserts an aggregate by delegating to persist with its id. */
|
|
25
|
-
async upsert(entity) {
|
|
26
|
-
await this.persist(entity.getId(), entity);
|
|
26
|
+
async upsert(entity, transaction) {
|
|
27
|
+
await this.persist(entity.getId(), entity, transaction);
|
|
27
28
|
}
|
|
28
29
|
/** Lists entities by criteria and returns a paginated response. */
|
|
29
30
|
async list(criteria, fieldsToExclude = []) {
|
|
@@ -37,9 +38,10 @@ class MongoRepository {
|
|
|
37
38
|
* @param {DeleteOptions} [options] - Optional parameters that modify the behavior of the delete operation.
|
|
38
39
|
* @return {Promise<void>} A promise that resolves when the deletion is complete, or rejects if an error occurs.
|
|
39
40
|
*/
|
|
40
|
-
async delete(filter, options) {
|
|
41
|
+
async delete(filter, options, transaction) {
|
|
41
42
|
const collection = await this.collection();
|
|
42
|
-
|
|
43
|
+
const session = MongoTransaction_1.MongoTransaction.sessionFor(transaction);
|
|
44
|
+
await collection.deleteMany(filter, session ? { ...options, session } : options);
|
|
43
45
|
}
|
|
44
46
|
async ensureIndexesOnce() {
|
|
45
47
|
const key = this.collectionName();
|
|
@@ -53,18 +55,17 @@ class MongoRepository {
|
|
|
53
55
|
await this.ensureIndexesOnce();
|
|
54
56
|
return this.collectionRaw();
|
|
55
57
|
}
|
|
56
|
-
async updateOne(filter, update) {
|
|
58
|
+
async updateOne(filter, update, transaction) {
|
|
57
59
|
const collection = await this.collection();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
});
|
|
60
|
+
const session = MongoTransaction_1.MongoTransaction.sessionFor(transaction);
|
|
61
|
+
await collection.updateOne(filter, update, session ? { upsert: true, session } : { upsert: true });
|
|
61
62
|
}
|
|
62
63
|
async collectionRaw() {
|
|
63
64
|
return (await MongoClientFactory_1.MongoClientFactory.createClient())
|
|
64
65
|
.db()
|
|
65
66
|
.collection(this.collectionName());
|
|
66
67
|
}
|
|
67
|
-
async persist(id, aggregateRoot) {
|
|
68
|
+
async persist(id, aggregateRoot, transaction) {
|
|
68
69
|
let primitives;
|
|
69
70
|
if (aggregateRoot.toPrimitives() instanceof Promise) {
|
|
70
71
|
primitives = await aggregateRoot.toPrimitives();
|
|
@@ -77,7 +78,7 @@ class MongoRepository {
|
|
|
77
78
|
...primitives,
|
|
78
79
|
id: id,
|
|
79
80
|
},
|
|
80
|
-
});
|
|
81
|
+
}, transaction);
|
|
81
82
|
}
|
|
82
83
|
async searchByCriteria(criteria, fieldsToExclude = []) {
|
|
83
84
|
this.criteria = criteria;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MongoTransaction = void 0;
|
|
4
|
+
const MongoClientFactory_1 = require("./MongoClientFactory");
|
|
5
|
+
const sessions = new WeakMap();
|
|
6
|
+
/**
|
|
7
|
+
* Context passed to repository writes that belong to the same MongoDB transaction.
|
|
8
|
+
*/
|
|
9
|
+
class MongoTransaction {
|
|
10
|
+
constructor(session) {
|
|
11
|
+
sessions.set(this, session);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Runs the callback in a MongoDB transaction and releases its session afterwards.
|
|
15
|
+
*/
|
|
16
|
+
static async run(callback) {
|
|
17
|
+
const client = await MongoClientFactory_1.MongoClientFactory.createClient();
|
|
18
|
+
const session = client.startSession();
|
|
19
|
+
const transaction = new MongoTransaction(session);
|
|
20
|
+
try {
|
|
21
|
+
return await session.withTransaction(() => callback(transaction));
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
await session.endSession();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** @internal Retrieves the driver session used by repository implementations. */
|
|
28
|
+
static sessionFor(transaction) {
|
|
29
|
+
return transaction ? sessions.get(transaction) : undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.MongoTransaction = MongoTransaction;
|
package/dist/cjs/mongo/index.js
CHANGED
|
@@ -18,3 +18,4 @@ __exportStar(require("./MongoCriteriaConverter"), exports);
|
|
|
18
18
|
__exportStar(require("./MongoRepository"), exports);
|
|
19
19
|
__exportStar(require("./IRepository"), exports);
|
|
20
20
|
__exportStar(require("./MongoClientFactory"), exports);
|
|
21
|
+
__exportStar(require("./MongoTransaction"), exports);
|
|
@@ -4,10 +4,11 @@ export class Criteria {
|
|
|
4
4
|
this.order = order;
|
|
5
5
|
this.limit = limit;
|
|
6
6
|
this.currentPage = offset;
|
|
7
|
-
|
|
8
|
-
offset
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
if (offset && limit) {
|
|
8
|
+
this.offset = offset > 0 ? (offset - 1) * limit : 0;
|
|
9
|
+
}
|
|
10
|
+
else
|
|
11
|
+
this.offset = undefined;
|
|
11
12
|
}
|
|
12
13
|
hasFilters() {
|
|
13
14
|
return this.filters.filters.length > 0;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MongoCriteriaConverter } from "./MongoCriteriaConverter";
|
|
2
2
|
import { MongoClientFactory } from "./MongoClientFactory";
|
|
3
|
+
import { MongoTransaction } from "./MongoTransaction";
|
|
3
4
|
import { ObjectId, } from "mongodb";
|
|
4
5
|
export class MongoRepository {
|
|
5
6
|
constructor(aggregateRootClass) {
|
|
@@ -19,8 +20,8 @@ export class MongoRepository {
|
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
/** Upserts an aggregate by delegating to persist with its id. */
|
|
22
|
-
async upsert(entity) {
|
|
23
|
-
await this.persist(entity.getId(), entity);
|
|
23
|
+
async upsert(entity, transaction) {
|
|
24
|
+
await this.persist(entity.getId(), entity, transaction);
|
|
24
25
|
}
|
|
25
26
|
/** Lists entities by criteria and returns a paginated response. */
|
|
26
27
|
async list(criteria, fieldsToExclude = []) {
|
|
@@ -34,9 +35,10 @@ export class MongoRepository {
|
|
|
34
35
|
* @param {DeleteOptions} [options] - Optional parameters that modify the behavior of the delete operation.
|
|
35
36
|
* @return {Promise<void>} A promise that resolves when the deletion is complete, or rejects if an error occurs.
|
|
36
37
|
*/
|
|
37
|
-
async delete(filter, options) {
|
|
38
|
+
async delete(filter, options, transaction) {
|
|
38
39
|
const collection = await this.collection();
|
|
39
|
-
|
|
40
|
+
const session = MongoTransaction.sessionFor(transaction);
|
|
41
|
+
await collection.deleteMany(filter, session ? { ...options, session } : options);
|
|
40
42
|
}
|
|
41
43
|
async ensureIndexesOnce() {
|
|
42
44
|
const key = this.collectionName();
|
|
@@ -50,18 +52,17 @@ export class MongoRepository {
|
|
|
50
52
|
await this.ensureIndexesOnce();
|
|
51
53
|
return this.collectionRaw();
|
|
52
54
|
}
|
|
53
|
-
async updateOne(filter, update) {
|
|
55
|
+
async updateOne(filter, update, transaction) {
|
|
54
56
|
const collection = await this.collection();
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
57
|
+
const session = MongoTransaction.sessionFor(transaction);
|
|
58
|
+
await collection.updateOne(filter, update, session ? { upsert: true, session } : { upsert: true });
|
|
58
59
|
}
|
|
59
60
|
async collectionRaw() {
|
|
60
61
|
return (await MongoClientFactory.createClient())
|
|
61
62
|
.db()
|
|
62
63
|
.collection(this.collectionName());
|
|
63
64
|
}
|
|
64
|
-
async persist(id, aggregateRoot) {
|
|
65
|
+
async persist(id, aggregateRoot, transaction) {
|
|
65
66
|
let primitives;
|
|
66
67
|
if (aggregateRoot.toPrimitives() instanceof Promise) {
|
|
67
68
|
primitives = await aggregateRoot.toPrimitives();
|
|
@@ -74,7 +75,7 @@ export class MongoRepository {
|
|
|
74
75
|
...primitives,
|
|
75
76
|
id: id,
|
|
76
77
|
},
|
|
77
|
-
});
|
|
78
|
+
}, transaction);
|
|
78
79
|
}
|
|
79
80
|
async searchByCriteria(criteria, fieldsToExclude = []) {
|
|
80
81
|
this.criteria = criteria;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { MongoClientFactory } from "./MongoClientFactory";
|
|
2
|
+
const sessions = new WeakMap();
|
|
3
|
+
/**
|
|
4
|
+
* Context passed to repository writes that belong to the same MongoDB transaction.
|
|
5
|
+
*/
|
|
6
|
+
export class MongoTransaction {
|
|
7
|
+
constructor(session) {
|
|
8
|
+
sessions.set(this, session);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Runs the callback in a MongoDB transaction and releases its session afterwards.
|
|
12
|
+
*/
|
|
13
|
+
static async run(callback) {
|
|
14
|
+
const client = await MongoClientFactory.createClient();
|
|
15
|
+
const session = client.startSession();
|
|
16
|
+
const transaction = new MongoTransaction(session);
|
|
17
|
+
try {
|
|
18
|
+
return await session.withTransaction(() => callback(transaction));
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
await session.endSession();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** @internal Retrieves the driver session used by repository implementations. */
|
|
25
|
+
static sessionFor(transaction) {
|
|
26
|
+
return transaction ? sessions.get(transaction) : undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/esm/mongo/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Criteria, Paginate } from "../criteria";
|
|
2
2
|
import { AggregateRoot } from "../AggregateRoot";
|
|
3
3
|
import { DeleteOptions } from "mongodb";
|
|
4
|
+
import { MongoTransaction } from "./MongoTransaction";
|
|
4
5
|
export interface IRepository<T extends AggregateRoot> {
|
|
5
6
|
one(filter: object): Promise<T | null>;
|
|
6
7
|
list<D>(criteria: Criteria, fieldsToExclude?: string[]): Promise<Paginate<D>>;
|
|
7
|
-
upsert(entity: T): Promise<void>;
|
|
8
|
-
delete(filter: object, options?: DeleteOptions): Promise<void>;
|
|
8
|
+
upsert(entity: T, transaction?: MongoTransaction): Promise<void>;
|
|
9
|
+
delete(filter: object, options?: DeleteOptions, transaction?: MongoTransaction): Promise<void>;
|
|
9
10
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MongoTransaction } from "./MongoTransaction";
|
|
1
2
|
import { Criteria, Paginate } from "../criteria";
|
|
2
3
|
import { AggregateRoot, AggregateRootClass } from "../AggregateRoot";
|
|
3
4
|
import { Collection, DeleteOptions, Document, UpdateFilter } from "mongodb";
|
|
@@ -12,7 +13,7 @@ export declare abstract class MongoRepository<T extends AggregateRoot> {
|
|
|
12
13
|
/** Finds a single entity and hydrates it via the aggregate's fromPrimitives. */
|
|
13
14
|
one(filter: object): Promise<T | null>;
|
|
14
15
|
/** Upserts an aggregate by delegating to persist with its id. */
|
|
15
|
-
upsert(entity: T): Promise<void>;
|
|
16
|
+
upsert(entity: T, transaction?: MongoTransaction): Promise<void>;
|
|
16
17
|
/** Lists entities by criteria and returns a paginated response. */
|
|
17
18
|
list<D>(criteria: Criteria, fieldsToExclude?: string[]): Promise<Paginate<D>>;
|
|
18
19
|
/**
|
|
@@ -22,11 +23,11 @@ export declare abstract class MongoRepository<T extends AggregateRoot> {
|
|
|
22
23
|
* @param {DeleteOptions} [options] - Optional parameters that modify the behavior of the delete operation.
|
|
23
24
|
* @return {Promise<void>} A promise that resolves when the deletion is complete, or rejects if an error occurs.
|
|
24
25
|
*/
|
|
25
|
-
delete(filter: object, options?: DeleteOptions): Promise<void>;
|
|
26
|
+
delete(filter: object, options?: DeleteOptions, transaction?: MongoTransaction): Promise<void>;
|
|
26
27
|
protected abstract ensureIndexes(collection: Collection): Promise<void>;
|
|
27
28
|
protected ensureIndexesOnce(): Promise<void>;
|
|
28
29
|
protected collection<U extends Document>(): Promise<Collection<U>>;
|
|
29
|
-
protected updateOne(filter: object, update: Document[] | UpdateFilter<any
|
|
30
|
+
protected updateOne(filter: object, update: Document[] | UpdateFilter<any>, transaction?: MongoTransaction): Promise<void>;
|
|
30
31
|
private collectionRaw;
|
|
31
32
|
private persist;
|
|
32
33
|
private searchByCriteria;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ClientSession } from "mongodb";
|
|
2
|
+
/**
|
|
3
|
+
* Context passed to repository writes that belong to the same MongoDB transaction.
|
|
4
|
+
*/
|
|
5
|
+
export declare class MongoTransaction {
|
|
6
|
+
private constructor();
|
|
7
|
+
/**
|
|
8
|
+
* Runs the callback in a MongoDB transaction and releases its session afterwards.
|
|
9
|
+
*/
|
|
10
|
+
static run<T>(callback: (transaction: MongoTransaction) => Promise<T>): Promise<T>;
|
|
11
|
+
/** @internal Retrieves the driver session used by repository implementations. */
|
|
12
|
+
static sessionFor(transaction?: MongoTransaction): ClientSession | undefined;
|
|
13
|
+
}
|
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.
|
|
4
|
+
"version": "1.9.0",
|
|
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",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"mongodb": "^7.0.0",
|
|
66
66
|
"prettier": "^3.8.3",
|
|
67
67
|
"rimraf": "^6.1.2",
|
|
68
|
-
"semantic-release": "^
|
|
68
|
+
"semantic-release": "^25.0.6",
|
|
69
69
|
"ts-jest": "^29.1.2",
|
|
70
70
|
"typescript": "^5.9.3"
|
|
71
71
|
},
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"url": "git+https://github.com/abejarano/ts-mongo-criteria.git"
|
|
82
82
|
},
|
|
83
83
|
"publishConfig": {
|
|
84
|
-
"registry": "https://registry.npmjs.org",
|
|
84
|
+
"registry": "https://registry.npmjs.org/",
|
|
85
85
|
"access": "public"
|
|
86
86
|
}
|
|
87
87
|
}
|