zync-nest-data-module 1.0.0 → 1.1.38
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/.prettierrc.json +23 -0
- package/README.md +226 -439
- package/dist/backup/backup.module.d.ts +1 -1
- package/dist/backup/backup.module.js +6 -6
- package/dist/backup/backup.module.js.map +1 -1
- package/dist/backup/backup.service.d.ts +1 -0
- package/dist/backup/backup.service.js +41 -18
- package/dist/backup/backup.service.js.map +1 -1
- package/dist/backup/backup.worker.d.ts +1 -0
- package/dist/backup/backup.worker.js +43 -0
- package/dist/backup/backup.worker.js.map +1 -0
- package/dist/base/dto.d.ts +19 -0
- package/dist/base/dto.js +86 -0
- package/dist/base/dto.js.map +1 -0
- package/dist/base/index.d.ts +2 -0
- package/dist/base/index.js +19 -0
- package/dist/base/index.js.map +1 -0
- package/dist/base/resolver.d.ts +6 -0
- package/dist/base/resolver.js +54 -0
- package/dist/base/resolver.js.map +1 -0
- package/dist/database/database.module.d.ts +5 -1
- package/dist/database/database.module.js +24 -19
- package/dist/database/database.module.js.map +1 -1
- package/dist/database/database.repository.d.ts +3 -2
- package/dist/database/database.repository.js +32 -25
- package/dist/database/database.repository.js.map +1 -1
- package/dist/database/database.scheme.d.ts +2 -8
- package/dist/database/database.scheme.js +2 -2
- package/dist/database/database.scheme.js.map +1 -1
- package/dist/database/database.service.d.ts +5 -3
- package/dist/database/database.service.js +44 -17
- package/dist/database/database.service.js.map +1 -1
- package/dist/database/database.transaction.d.ts +7 -7
- package/dist/database/database.transaction.js +61 -50
- package/dist/database/database.transaction.js.map +1 -1
- package/dist/database/database.uniqueId.d.ts +8 -3
- package/dist/database/database.uniqueId.js +26 -7
- package/dist/database/database.uniqueId.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/redis/index.d.ts +2 -0
- package/dist/redis/index.js +19 -0
- package/dist/redis/index.js.map +1 -0
- package/dist/redis/redis.module.d.ts +2 -0
- package/dist/redis/redis.module.js +43 -0
- package/dist/redis/redis.module.js.map +1 -0
- package/dist/redis/redis.service.d.ts +27 -0
- package/dist/redis/redis.service.js +126 -0
- package/dist/redis/redis.service.js.map +1 -0
- package/dist/service/service.d.ts +2 -2
- package/dist/service/service.js +42 -56
- package/dist/service/service.js.map +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/libs/src/app.controller.ts +53 -46
- package/libs/src/app.module.ts +13 -10
- package/libs/src/backup/backup.module.ts +2 -2
- package/libs/src/backup/backup.service.ts +60 -69
- package/libs/src/backup/backup.worker.ts +35 -0
- package/libs/src/base/dto.ts +46 -0
- package/libs/src/base/index.ts +2 -0
- package/libs/src/base/resolver.ts +20 -0
- package/libs/src/database/database.module.ts +24 -22
- package/libs/src/database/database.repository.ts +47 -50
- package/libs/src/database/database.scheme.ts +2 -2
- package/libs/src/database/database.service.ts +61 -22
- package/libs/src/database/database.transaction.ts +62 -64
- package/libs/src/database/database.uniqueId.ts +39 -8
- package/libs/src/index.ts +3 -1
- package/libs/src/main.ts +8 -18
- package/libs/src/redis/index.ts +2 -0
- package/libs/src/redis/redis.interface.ts +34 -0
- package/libs/src/redis/redis.module.ts +30 -0
- package/libs/src/redis/redis.service.ts +137 -0
- package/libs/src/service/service.ts +48 -68
- package/libs/src/test/test.controller.ts +59 -0
- package/libs/src/test/test.module.ts +8 -4
- package/libs/src/test/test.redis.ts +19 -0
- package/libs/src/test/test.resolver.ts +10 -19
- package/package.json +45 -26
|
@@ -1,17 +1,12 @@
|
|
|
1
|
+
import { Inject } from "@nestjs/common";
|
|
1
2
|
import { InjectModel } from "@nestjs/mongoose";
|
|
2
3
|
import moment from "moment";
|
|
3
4
|
import { Document, FilterQuery, PipelineStage, Schema, Types } from "mongoose";
|
|
4
5
|
import { SoftDeleteModel } from "mongoose-delete";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
handlePageResult,
|
|
8
|
-
IPageParams,
|
|
9
|
-
IPageResult,
|
|
10
|
-
} from "./database.scheme";
|
|
11
|
-
import { TransactionSession } from "./database.transaction";
|
|
6
|
+
import { handlePageFacet, handlePageResult, IPageParams, IPageResult } from "./database.scheme";
|
|
7
|
+
import { TransactionManager, TransactionSession } from "./database.transaction";
|
|
12
8
|
import { ApUniqueIdGenerator, UniqueKeyTypes } from "./database.uniqueId";
|
|
13
9
|
import { DbUtils } from "./database.utils";
|
|
14
|
-
import { Inject } from '@nestjs/common';
|
|
15
10
|
|
|
16
11
|
export interface IRefInput<T> {
|
|
17
12
|
prefix?: string;
|
|
@@ -19,33 +14,28 @@ export interface IRefInput<T> {
|
|
|
19
14
|
filter?: FilterQuery<T>;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
export abstract class AbstractBaseRepository<
|
|
23
|
-
T extends Omit<Document<any, any, any>, "delete">
|
|
24
|
-
> {
|
|
17
|
+
export abstract class AbstractBaseRepository<T extends Omit<Document<any, any, any>, "delete">> {
|
|
25
18
|
private _session: TransactionSession = null;
|
|
26
19
|
|
|
27
20
|
@Inject(ApUniqueIdGenerator)
|
|
28
|
-
private uniqueIdGenerator: ApUniqueIdGenerator
|
|
21
|
+
private uniqueIdGenerator: ApUniqueIdGenerator;
|
|
29
22
|
|
|
30
23
|
constructor(
|
|
31
24
|
@InjectModel("MODEL_NAME")
|
|
32
25
|
protected dbModel: SoftDeleteModel<T>
|
|
33
|
-
) {}
|
|
26
|
+
) { }
|
|
34
27
|
|
|
35
28
|
protected abstract buildQuery(query: Partial<T>): any;
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
public set session(v: TransactionSession) {
|
|
40
|
-
this._session = v;
|
|
41
|
-
}
|
|
30
|
+
protected abstract mapData?(data: any, isCreate: boolean): Promise<T>;
|
|
42
31
|
|
|
43
32
|
public get session(): TransactionSession {
|
|
44
|
-
return
|
|
33
|
+
return TransactionManager.getCurrentSession();
|
|
45
34
|
}
|
|
46
35
|
|
|
47
36
|
private get hasSession(): boolean {
|
|
48
|
-
|
|
37
|
+
const session = this.session;
|
|
38
|
+
return !!session && !session.hasEnded;
|
|
49
39
|
}
|
|
50
40
|
|
|
51
41
|
protected mapIds(query: Partial<T>): any {
|
|
@@ -85,8 +75,7 @@ export abstract class AbstractBaseRepository<
|
|
|
85
75
|
}
|
|
86
76
|
|
|
87
77
|
public async aggregate(pipeline?: PipelineStage[]): Promise<T[]> {
|
|
88
|
-
if (!this.hasSession)
|
|
89
|
-
return await this.dbModel.aggregate(pipeline).session(this.session);
|
|
78
|
+
if (!this.hasSession) return await this.dbModel.aggregate(pipeline).session(this.session);
|
|
90
79
|
|
|
91
80
|
return await this.dbModel.aggregate(pipeline).session(this.session);
|
|
92
81
|
}
|
|
@@ -110,11 +99,11 @@ export abstract class AbstractBaseRepository<
|
|
|
110
99
|
}
|
|
111
100
|
|
|
112
101
|
protected async _create(data: Partial<T> | T): Promise<T> {
|
|
113
|
-
const payload = await this.
|
|
102
|
+
const payload: any = await this._mapData(data, true);
|
|
114
103
|
|
|
115
|
-
payload.
|
|
116
|
-
|
|
117
|
-
|
|
104
|
+
payload.ref = payload.ref || (await this.generateRef());
|
|
105
|
+
|
|
106
|
+
payload._id = payload._id ? new Types.ObjectId(payload._id?.toString()) : this.getObjectId();
|
|
118
107
|
|
|
119
108
|
const created = new this.dbModel(payload);
|
|
120
109
|
|
|
@@ -158,9 +147,7 @@ export abstract class AbstractBaseRepository<
|
|
|
158
147
|
|
|
159
148
|
query = this.mapIds(query) as FilterQuery<T>;
|
|
160
149
|
|
|
161
|
-
const findQuery = this.dbModel
|
|
162
|
-
.find(query as FilterQuery<T>)
|
|
163
|
-
.sort({ createdAt: -1 });
|
|
150
|
+
const findQuery = this.dbModel.find(query as FilterQuery<T>).sort({ createdAt: -1 });
|
|
164
151
|
|
|
165
152
|
if (this.hasSession) {
|
|
166
153
|
findQuery.session(this.session);
|
|
@@ -172,14 +159,14 @@ export abstract class AbstractBaseRepository<
|
|
|
172
159
|
protected async _update(id: string, data: Partial<T>): Promise<T> {
|
|
173
160
|
delete data._id;
|
|
174
161
|
|
|
175
|
-
const payload = await this.
|
|
162
|
+
const payload = await this._mapData(data, false);
|
|
176
163
|
|
|
177
164
|
await this.dbModel
|
|
178
165
|
.updateOne(
|
|
179
166
|
{ _id: new Types.ObjectId(id) },
|
|
180
167
|
{ $set: payload },
|
|
181
168
|
{
|
|
182
|
-
...(this.hasSession && { session: this.session })
|
|
169
|
+
...(this.hasSession && { session: this.session })
|
|
183
170
|
}
|
|
184
171
|
)
|
|
185
172
|
.exec();
|
|
@@ -187,10 +174,25 @@ export abstract class AbstractBaseRepository<
|
|
|
187
174
|
return await this.findById(id);
|
|
188
175
|
}
|
|
189
176
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
177
|
+
private async _mapData(data: Partial<T>, isNew: boolean): Promise<Partial<T>> {
|
|
178
|
+
const payload: any = await this.mapData(data, isNew);
|
|
179
|
+
|
|
180
|
+
payload.documentNo = payload.documentNo || (await this.generateRef());
|
|
181
|
+
payload.documentDate = payload.documentDate ? moment(payload.documentDate).valueOf() : moment().valueOf();
|
|
182
|
+
|
|
183
|
+
if (isNew) {
|
|
184
|
+
// payload.createdBy = payload.createdBy || this.contextUser?._id;
|
|
185
|
+
payload.ref = payload.ref || (await this.generateRef());
|
|
186
|
+
payload.createdAt = payload.createdAt ? moment(payload.createdAt).valueOf() : moment().valueOf();
|
|
187
|
+
} else {
|
|
188
|
+
// payload.updatedBy = payload.updatedBy || this.contextUser?._id;
|
|
189
|
+
payload.updatedAt = payload.updatedAt ? moment(payload.updatedAt).valueOf() : moment().valueOf();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return this.mapIds(payload);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected async _updateMany(query: Partial<T>, model: Partial<T>): Promise<void> {
|
|
194
196
|
delete model._id;
|
|
195
197
|
|
|
196
198
|
query = this.mapIds(query) as FilterQuery<T>;
|
|
@@ -202,7 +204,7 @@ export abstract class AbstractBaseRepository<
|
|
|
202
204
|
query as FilterQuery<T>,
|
|
203
205
|
{
|
|
204
206
|
...model,
|
|
205
|
-
updatedAt: Date.now()
|
|
207
|
+
updatedAt: Date.now()
|
|
206
208
|
},
|
|
207
209
|
this.hasSession ? { new: true, session: this.session } : {}
|
|
208
210
|
)
|
|
@@ -248,16 +250,13 @@ export abstract class AbstractBaseRepository<
|
|
|
248
250
|
return result.length === 0 ? null : result[0];
|
|
249
251
|
}
|
|
250
252
|
|
|
251
|
-
protected async _page(
|
|
252
|
-
page: IPageParams,
|
|
253
|
-
$lookups?: any[]
|
|
254
|
-
): Promise<IPageResult<T>> {
|
|
253
|
+
protected async _page(page: IPageParams, $lookups?: any[]): Promise<IPageResult<T>> {
|
|
255
254
|
const query = this.buildQuery(page as any);
|
|
256
255
|
return this.aggregate([
|
|
257
256
|
...(!!$lookups?.length ? $lookups : []),
|
|
258
257
|
...(Array.isArray(query) ? query : [query]),
|
|
259
258
|
{ $sort: { createdAt: -1 } },
|
|
260
|
-
{ ...handlePageFacet(page) }
|
|
259
|
+
{ ...handlePageFacet(page) }
|
|
261
260
|
]).then(handlePageResult<T>);
|
|
262
261
|
}
|
|
263
262
|
|
|
@@ -274,6 +273,7 @@ export abstract class AbstractBaseRepository<
|
|
|
274
273
|
>;
|
|
275
274
|
searchKeys?: string[];
|
|
276
275
|
ignoreKeys?: string[];
|
|
276
|
+
dateKey?: string;
|
|
277
277
|
},
|
|
278
278
|
addMatchCondition: (condition: any) => void
|
|
279
279
|
) {
|
|
@@ -282,10 +282,7 @@ export abstract class AbstractBaseRepository<
|
|
|
282
282
|
Object.keys(schema.obj).forEach((key) => {
|
|
283
283
|
if (query[key] && !ignoreKeys?.includes(key)) {
|
|
284
284
|
addMatchCondition({
|
|
285
|
-
[key]:
|
|
286
|
-
schema.path(key).instance === "Mixed"
|
|
287
|
-
? this.toObjectId(query[key])
|
|
288
|
-
: query[key],
|
|
285
|
+
[key]: schema.path(key).instance === "Mixed" ? this.toObjectId(query[key]) : query[key]
|
|
289
286
|
});
|
|
290
287
|
}
|
|
291
288
|
});
|
|
@@ -293,7 +290,7 @@ export abstract class AbstractBaseRepository<
|
|
|
293
290
|
if (query?.keyword && options.searchKeys?.length) {
|
|
294
291
|
const keywordValue = query?.keyword;
|
|
295
292
|
const globalSearchConditions = options.searchKeys?.map((field) => ({
|
|
296
|
-
[field]: { $regex: keywordValue, $options: "i" }
|
|
293
|
+
[field]: { $regex: keywordValue, $options: "i" }
|
|
297
294
|
}));
|
|
298
295
|
|
|
299
296
|
if (globalSearchConditions.length > 0) {
|
|
@@ -303,16 +300,16 @@ export abstract class AbstractBaseRepository<
|
|
|
303
300
|
|
|
304
301
|
if (query?.fromDate && query?.toDate) {
|
|
305
302
|
addMatchCondition({
|
|
306
|
-
createdAt: {
|
|
303
|
+
[options.dateKey || "createdAt"]: {
|
|
307
304
|
$gte: moment(query.fromDate).startOf("day").valueOf(),
|
|
308
|
-
$lte: moment(query.toDate).endOf("day").valueOf()
|
|
309
|
-
}
|
|
305
|
+
$lte: moment(query.toDate).endOf("day").valueOf()
|
|
306
|
+
}
|
|
310
307
|
});
|
|
311
308
|
}
|
|
312
309
|
|
|
313
310
|
if (query?.ref) {
|
|
314
311
|
addMatchCondition({
|
|
315
|
-
ref: { $regex: query.ref?.toUpperCase(), $options: "i" }
|
|
312
|
+
ref: { $regex: query.ref?.toUpperCase(), $options: "i" }
|
|
316
313
|
});
|
|
317
314
|
}
|
|
318
315
|
}
|
|
@@ -109,10 +109,10 @@ export interface IPageResult<T> {
|
|
|
109
109
|
data: Array<T>;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
export const handlePageFacet = (page: any) => {
|
|
112
|
+
export const handlePageFacet = (page: any, $lookups?: any[]) => {
|
|
113
113
|
return {
|
|
114
114
|
$facet: {
|
|
115
|
-
data: [{ $skip: Number(page.skip) }, { $limit: Number(page.take) }],
|
|
115
|
+
data: [{ $skip: Number(page.skip) }, { $limit: Number(page.take) }, ...($lookups || [])],
|
|
116
116
|
totalRecords: [{ $count: "count" }],
|
|
117
117
|
},
|
|
118
118
|
};
|
|
@@ -1,36 +1,75 @@
|
|
|
1
|
-
import { Injectable } from "@nestjs/common";
|
|
2
|
-
import { Connection
|
|
1
|
+
import { Injectable, Logger } from "@nestjs/common";
|
|
2
|
+
import mongoose, { Connection } from "mongoose";
|
|
3
3
|
|
|
4
|
-
@Injectable(
|
|
4
|
+
@Injectable()
|
|
5
5
|
export class DatabaseService {
|
|
6
|
+
private readonly logger = new Logger(DatabaseService.name);
|
|
7
|
+
private readonly connections: Map<string, Promise<Connection>> = new Map();
|
|
6
8
|
private _connectionId: string = process.env.mongodb_default_db_name;
|
|
7
|
-
private connections: Map<string, Connection> = new Map();
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Create or retrieve a connection for a given DB name (company/module)
|
|
12
|
+
*/
|
|
13
|
+
public async setConnection(companyId?: string): Promise<Connection> {
|
|
14
|
+
const dbName = companyId || process.env.mongodb_default_db_name;
|
|
13
15
|
|
|
14
|
-
//
|
|
15
|
-
if (this.connections.has(
|
|
16
|
-
return this.connections.get(
|
|
16
|
+
// ✅ Reuse existing pending/established connection
|
|
17
|
+
if (this.connections.has(dbName)) {
|
|
18
|
+
return this.connections.get(dbName);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const mongoUri = process.env.mongodb_url?.replace("{{db_name}}", dbName);
|
|
22
|
+
if (!mongoUri) {
|
|
23
|
+
throw new Error("mongodb_url not defined in environment variables");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.logger.log(`Connecting to MongoDB: ${dbName}`);
|
|
27
|
+
|
|
28
|
+
// ✅ Store promise immediately to prevent race conditions
|
|
29
|
+
const connectionPromise = mongoose.createConnection(mongoUri).asPromise();
|
|
30
|
+
|
|
31
|
+
this.connections.set(dbName, connectionPromise);
|
|
32
|
+
|
|
33
|
+
// ✅ Wait until fully connected
|
|
34
|
+
const connection = await connectionPromise;
|
|
23
35
|
|
|
24
|
-
|
|
36
|
+
connection.on("connected", () =>
|
|
37
|
+
this.logger.log(`✅ MongoDB connected: ${dbName}`)
|
|
38
|
+
);
|
|
39
|
+
connection.on("disconnected", () => {
|
|
40
|
+
this.logger.warn(`⚠️ MongoDB disconnected: ${dbName}`);
|
|
41
|
+
this.connections.delete(dbName);
|
|
42
|
+
});
|
|
43
|
+
connection.on("error", (err) => {
|
|
44
|
+
this.logger.error(`❌ MongoDB error on ${dbName}: ${err.message}`);
|
|
45
|
+
this.connections.delete(dbName);
|
|
46
|
+
});
|
|
25
47
|
|
|
26
48
|
return connection;
|
|
27
49
|
}
|
|
28
50
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Retrieve an existing connection, or create one if missing
|
|
53
|
+
*/
|
|
54
|
+
public async getConnection(companyId?: string): Promise<Connection> {
|
|
55
|
+
const dbName = companyId || this._connectionId;
|
|
56
|
+
|
|
57
|
+
if (this.connections.has(dbName)) {
|
|
58
|
+
return this.connections.get(dbName);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this.setConnection(dbName);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Gracefully close all open connections
|
|
66
|
+
*/
|
|
67
|
+
public async closeAll(): Promise<void> {
|
|
68
|
+
for (const [dbName, connPromise] of this.connections.entries()) {
|
|
69
|
+
const conn = await connPromise;
|
|
70
|
+
await conn.close();
|
|
71
|
+
this.logger.log(`Closed MongoDB connection: ${dbName}`);
|
|
33
72
|
}
|
|
34
|
-
|
|
73
|
+
this.connections.clear();
|
|
35
74
|
}
|
|
36
|
-
}
|
|
75
|
+
}
|
|
@@ -1,101 +1,99 @@
|
|
|
1
|
-
import { Injectable, Scope } from "@nestjs/common";
|
|
1
|
+
import { Injectable, Scope, Logger } from "@nestjs/common";
|
|
2
2
|
import { InjectConnection } from "@nestjs/mongoose";
|
|
3
|
-
import { MongoServerError } from "mongodb"; // Import MongoServerError if using native MongoDB driver
|
|
4
3
|
import mongodb, { ClientSession, Connection } from "mongoose";
|
|
5
|
-
import {
|
|
4
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
6
5
|
|
|
7
6
|
export type TransactionSession = mongodb.ClientSession;
|
|
8
7
|
|
|
9
8
|
@Injectable({ scope: Scope.DEFAULT })
|
|
10
9
|
export class TransactionManager {
|
|
11
|
-
private
|
|
12
|
-
private
|
|
13
|
-
constructor(
|
|
14
|
-
@InjectConnection() private readonly connection: Connection,
|
|
15
|
-
private dbSvc: DatabaseService
|
|
16
|
-
) { }
|
|
10
|
+
private readonly logger = new Logger(TransactionManager.name);
|
|
11
|
+
private static readonly storage = new AsyncLocalStorage<{ session: ClientSession; sessionName?: string }>();
|
|
17
12
|
|
|
18
|
-
private
|
|
19
|
-
|
|
13
|
+
constructor(@InjectConnection() private readonly connection: Connection) { }
|
|
14
|
+
|
|
15
|
+
public run<T>(callback: () => Promise<T>): Promise<T> {
|
|
16
|
+
return TransactionManager.storage.run({ session: null }, callback);
|
|
20
17
|
}
|
|
21
18
|
|
|
22
|
-
public
|
|
23
|
-
|
|
19
|
+
public static getCurrentSession(): ClientSession | null {
|
|
20
|
+
const store = TransactionManager.storage.getStore();
|
|
21
|
+
return store?.session || null;
|
|
22
|
+
}
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
private async getSession(): Promise<ClientSession> {
|
|
25
|
+
const store = TransactionManager.storage.getStore();
|
|
26
|
+
if (!store) {
|
|
27
|
+
throw new Error("[TransactionManager] Context not initialized. Use run() method.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (store.session) return store.session;
|
|
31
|
+
|
|
32
|
+
store.session = await this.connection.startSession();
|
|
33
|
+
store.session.startTransaction({
|
|
34
|
+
readConcern: { level: "snapshot" },
|
|
35
|
+
writeConcern: { w: "majority" },
|
|
29
36
|
});
|
|
30
|
-
return
|
|
37
|
+
return store.session as TransactionSession;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
|
-
public async
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
59
|
-
console.warn(
|
|
60
|
-
`Write conflict detected. Retrying commit (${attempt}/${retries})...`
|
|
61
|
-
);
|
|
62
|
-
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for 1 second before retrying
|
|
63
|
-
} else {
|
|
64
|
-
throw error;
|
|
65
|
-
}
|
|
66
|
-
} finally {
|
|
67
|
-
await this._session.endSession();
|
|
68
|
-
// await this.dbSvc.getConnection().close();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
40
|
+
public async startTransaction(): Promise<TransactionSession> {
|
|
41
|
+
return await this.getSession();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public async commitTransaction(): Promise<void> {
|
|
45
|
+
const store = TransactionManager.storage.getStore();
|
|
46
|
+
if (!store?.session) {
|
|
47
|
+
throw new Error("[TransactionManager] No session available");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!store.session.transaction.isActive) {
|
|
51
|
+
this.logger.warn("[TransactionManager] No active transaction to commit"); // Changed to warn
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await store.session.commitTransaction();
|
|
57
|
+
this.logger.log("[TransactionManager] Transaction committed successfully.");
|
|
58
|
+
} catch (error) {
|
|
59
|
+
await this.abortTransaction();
|
|
60
|
+
throw error;
|
|
61
|
+
} finally {
|
|
62
|
+
this.logger.log("[TransactionManager] Ending transaction...");
|
|
63
|
+
await this.endTransaction();
|
|
71
64
|
}
|
|
72
65
|
}
|
|
73
66
|
|
|
74
67
|
public async abortTransaction(): Promise<void> {
|
|
75
|
-
|
|
68
|
+
const store = TransactionManager.storage.getStore();
|
|
69
|
+
if (store?.session && store.session.transaction.isActive) {
|
|
76
70
|
try {
|
|
77
|
-
await
|
|
78
|
-
await this._session.endSession();
|
|
71
|
+
await store.session.abortTransaction();
|
|
79
72
|
} catch (error) {
|
|
80
73
|
} finally {
|
|
81
|
-
await this.
|
|
74
|
+
await this.endTransaction();
|
|
82
75
|
}
|
|
83
76
|
}
|
|
84
77
|
}
|
|
85
78
|
|
|
86
79
|
public async endTransaction(): Promise<void> {
|
|
87
|
-
|
|
80
|
+
const store = TransactionManager.storage.getStore();
|
|
81
|
+
if (store?.session) {
|
|
88
82
|
try {
|
|
89
|
-
await
|
|
83
|
+
await store.session.endSession();
|
|
90
84
|
} catch (error) { }
|
|
85
|
+
store.session = null;
|
|
91
86
|
}
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
public get sessionName(): string {
|
|
95
|
-
return
|
|
90
|
+
return TransactionManager.storage.getStore()?.sessionName;
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
public set sessionName(name: string) {
|
|
99
|
-
|
|
94
|
+
const store = TransactionManager.storage.getStore();
|
|
95
|
+
if (store) {
|
|
96
|
+
store.sessionName = name;
|
|
97
|
+
}
|
|
100
98
|
}
|
|
101
99
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { Injectable } from "@nestjs/common";
|
|
2
|
-
import { InjectModel, Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
|
1
|
+
import { Inject, Injectable, OnModuleInit } from "@nestjs/common";
|
|
2
|
+
import { getModelToken, InjectModel, Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
|
|
3
3
|
import { Document, Types } from "mongoose";
|
|
4
4
|
import { SoftDeleteModel } from "mongoose-delete";
|
|
5
|
+
import { CONNECTION_NAME } from "./database.module";
|
|
6
|
+
import { ModuleRef } from "@nestjs/core";
|
|
5
7
|
|
|
6
8
|
@Schema({ collection: "unique_ids" })
|
|
7
9
|
export class UniqueId {
|
|
@@ -18,15 +20,42 @@ export const UniqueIdSchema = SchemaFactory.createForClass(UniqueId);
|
|
|
18
20
|
export type UniqueKeyTypes = "users" | "uniqueId";
|
|
19
21
|
|
|
20
22
|
@Injectable({})
|
|
21
|
-
export class ApUniqueIdGenerator {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
export class ApUniqueIdGenerator implements OnModuleInit {
|
|
24
|
+
private model: SoftDeleteModel<UniqueId>;
|
|
25
|
+
|
|
26
|
+
constructor(@Inject(CONNECTION_NAME) private readonly connName: string, private readonly moduleRef: ModuleRef) {}
|
|
27
|
+
// constructor(
|
|
28
|
+
// @InjectModel(UniqueId.name)
|
|
29
|
+
// private model: SoftDeleteModel<UniqueIdDocument>
|
|
30
|
+
// ) {}
|
|
31
|
+
onModuleInit() {
|
|
32
|
+
this.ensureModelInitialized();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private ensureModelInitialized(): void {
|
|
36
|
+
if (!this.model) {
|
|
37
|
+
// Lazy initialization as fallback
|
|
38
|
+
this.model = this.connName
|
|
39
|
+
? this.moduleRef.get<SoftDeleteModel<UniqueId>>(getModelToken(UniqueId.name, this.connName), {
|
|
40
|
+
strict: false
|
|
41
|
+
})
|
|
42
|
+
: this.moduleRef.get<SoftDeleteModel<UniqueId>>(getModelToken(UniqueId.name), {
|
|
43
|
+
strict: false
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!this.model) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`UniqueId model is not initialized. Please ensure MongooseModule.forFeature includes UniqueId schema with connection name: ${
|
|
49
|
+
this.connName || "default"
|
|
50
|
+
}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
27
54
|
}
|
|
28
55
|
|
|
29
56
|
public async getNextUniqueId(key: string = "uniqueId"): Promise<number> {
|
|
57
|
+
this.ensureModelInitialized();
|
|
58
|
+
|
|
30
59
|
const result = await this.model.findOneAndUpdate(
|
|
31
60
|
{ key },
|
|
32
61
|
{ $inc: { sequence_value: 1 } },
|
|
@@ -45,6 +74,8 @@ export class ApUniqueIdGenerator {
|
|
|
45
74
|
}
|
|
46
75
|
|
|
47
76
|
public async seed(): Promise<void> {
|
|
77
|
+
this.ensureModelInitialized();
|
|
78
|
+
|
|
48
79
|
const keys = [{ key: "uniqueId", sequence_value: 1001 }];
|
|
49
80
|
|
|
50
81
|
for await (const key of keys) {
|
package/libs/src/index.ts
CHANGED
package/libs/src/main.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { NestFactory } from "@nestjs/core";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { ApDbModule } from "./app.module";
|
|
2
|
+
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
|
3
|
+
import { ZyncNestDataModule } from "./app.module";
|
|
5
4
|
|
|
6
5
|
async function bootstrap() {
|
|
7
|
-
const app = await NestFactory.create(
|
|
6
|
+
const app = await NestFactory.create(ZyncNestDataModule);
|
|
8
7
|
|
|
9
8
|
// Enable CORS
|
|
10
9
|
app.enableCors({
|
|
@@ -12,26 +11,17 @@ async function bootstrap() {
|
|
|
12
11
|
credentials: true,
|
|
13
12
|
});
|
|
14
13
|
|
|
15
|
-
// // Global validation pipe
|
|
16
|
-
// app.useGlobalPipes(
|
|
17
|
-
// new ValidationPipe({
|
|
18
|
-
// transform: true,
|
|
19
|
-
// whitelist: true,
|
|
20
|
-
// forbidNonWhitelisted: true,
|
|
21
|
-
// })
|
|
22
|
-
// );
|
|
23
|
-
|
|
24
14
|
// Swagger configuration
|
|
25
15
|
const config = new DocumentBuilder()
|
|
26
|
-
.setTitle("Zync Nest
|
|
27
|
-
.setDescription("API documentation for Zync Nest
|
|
16
|
+
.setTitle("Zync Nest Data Module API")
|
|
17
|
+
.setDescription("API documentation for Zync Nest Data Module")
|
|
28
18
|
.setVersion("1.0.23")
|
|
29
|
-
.addTag("
|
|
19
|
+
.addTag("Data Module", "Data Module endpoints for Zync Nest Data Module")
|
|
30
20
|
.build();
|
|
31
21
|
|
|
32
22
|
const document = SwaggerModule.createDocument(app, config);
|
|
33
23
|
SwaggerModule.setup("api", app, document, {
|
|
34
|
-
customSiteTitle: "Zync Nest
|
|
24
|
+
customSiteTitle: "Zync Nest Data Module API",
|
|
35
25
|
customfavIcon: "https://nestjs.com/img/logo-small.svg",
|
|
36
26
|
customJs: [
|
|
37
27
|
"https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-bundle.js",
|
|
@@ -46,7 +36,7 @@ async function bootstrap() {
|
|
|
46
36
|
await app.listen(port);
|
|
47
37
|
|
|
48
38
|
console.log(
|
|
49
|
-
`🚀 Zync Nest
|
|
39
|
+
`🚀 Zync Nest Data Module server is running on: http://localhost:${port}`
|
|
50
40
|
);
|
|
51
41
|
console.log(
|
|
52
42
|
`📚 Swagger documentation available at: http://localhost:${port}/api`
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface IRedisSearchParams {
|
|
2
|
+
query?: string;
|
|
3
|
+
minScore?: number;
|
|
4
|
+
maxScore?: number;
|
|
5
|
+
keyPrefix?: string;
|
|
6
|
+
sortBy: {
|
|
7
|
+
field: string;
|
|
8
|
+
order?: "asc" | "desc";
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IRedisPageParams extends IRedisSearchParams {
|
|
13
|
+
page: number; // 1-based page
|
|
14
|
+
pageSize: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface IRedisSearchResult<T> {
|
|
18
|
+
data: T[];
|
|
19
|
+
totalRecords: number;
|
|
20
|
+
page?: number;
|
|
21
|
+
totalPages?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IRedisZSetItem {
|
|
25
|
+
key: string;
|
|
26
|
+
score: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IRedisSchemaField {
|
|
30
|
+
name: string;
|
|
31
|
+
type: "TEXT" | "NUMERIC" | "TAG"; // supported Redisearch types
|
|
32
|
+
sortable?: boolean; // if field can be used in SORTBY
|
|
33
|
+
noIndex?: boolean; // optional, skip indexing
|
|
34
|
+
}
|