@abejarano/ts-mongodb-criteria 1.8.6 → 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 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
@@ -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
- await collection.deleteMany(filter, options);
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
- await collection.updateOne(filter, update, {
59
- upsert: true,
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;
@@ -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);
@@ -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
- await collection.deleteMany(filter, options);
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
- await collection.updateOne(filter, update, {
56
- upsert: true,
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
+ }
@@ -2,3 +2,4 @@ export * from "./MongoCriteriaConverter";
2
2
  export * from "./MongoRepository";
3
3
  export * from "./IRepository";
4
4
  export * from "./MongoClientFactory";
5
+ export * from "./MongoTransaction";
@@ -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>): Promise<void>;
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
+ }
@@ -2,3 +2,4 @@ export * from "./MongoCriteriaConverter";
2
2
  export * from "./MongoRepository";
3
3
  export * from "./IRepository";
4
4
  export * from "./MongoClientFactory";
5
+ export * from "./MongoTransaction";
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.8.6",
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": "^24.2.9",
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
  }