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.
Files changed (80) hide show
  1. package/.prettierrc.json +23 -0
  2. package/README.md +226 -439
  3. package/dist/backup/backup.module.d.ts +1 -1
  4. package/dist/backup/backup.module.js +6 -6
  5. package/dist/backup/backup.module.js.map +1 -1
  6. package/dist/backup/backup.service.d.ts +1 -0
  7. package/dist/backup/backup.service.js +41 -18
  8. package/dist/backup/backup.service.js.map +1 -1
  9. package/dist/backup/backup.worker.d.ts +1 -0
  10. package/dist/backup/backup.worker.js +43 -0
  11. package/dist/backup/backup.worker.js.map +1 -0
  12. package/dist/base/dto.d.ts +19 -0
  13. package/dist/base/dto.js +86 -0
  14. package/dist/base/dto.js.map +1 -0
  15. package/dist/base/index.d.ts +2 -0
  16. package/dist/base/index.js +19 -0
  17. package/dist/base/index.js.map +1 -0
  18. package/dist/base/resolver.d.ts +6 -0
  19. package/dist/base/resolver.js +54 -0
  20. package/dist/base/resolver.js.map +1 -0
  21. package/dist/database/database.module.d.ts +5 -1
  22. package/dist/database/database.module.js +24 -19
  23. package/dist/database/database.module.js.map +1 -1
  24. package/dist/database/database.repository.d.ts +3 -2
  25. package/dist/database/database.repository.js +32 -25
  26. package/dist/database/database.repository.js.map +1 -1
  27. package/dist/database/database.scheme.d.ts +2 -8
  28. package/dist/database/database.scheme.js +2 -2
  29. package/dist/database/database.scheme.js.map +1 -1
  30. package/dist/database/database.service.d.ts +5 -3
  31. package/dist/database/database.service.js +44 -17
  32. package/dist/database/database.service.js.map +1 -1
  33. package/dist/database/database.transaction.d.ts +7 -7
  34. package/dist/database/database.transaction.js +61 -50
  35. package/dist/database/database.transaction.js.map +1 -1
  36. package/dist/database/database.uniqueId.d.ts +8 -3
  37. package/dist/database/database.uniqueId.js +26 -7
  38. package/dist/database/database.uniqueId.js.map +1 -1
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.js +2 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/redis/index.d.ts +2 -0
  43. package/dist/redis/index.js +19 -0
  44. package/dist/redis/index.js.map +1 -0
  45. package/dist/redis/redis.module.d.ts +2 -0
  46. package/dist/redis/redis.module.js +43 -0
  47. package/dist/redis/redis.module.js.map +1 -0
  48. package/dist/redis/redis.service.d.ts +27 -0
  49. package/dist/redis/redis.service.js +126 -0
  50. package/dist/redis/redis.service.js.map +1 -0
  51. package/dist/service/service.d.ts +2 -2
  52. package/dist/service/service.js +42 -56
  53. package/dist/service/service.js.map +1 -1
  54. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  55. package/libs/src/app.controller.ts +53 -46
  56. package/libs/src/app.module.ts +13 -10
  57. package/libs/src/backup/backup.module.ts +2 -2
  58. package/libs/src/backup/backup.service.ts +60 -69
  59. package/libs/src/backup/backup.worker.ts +35 -0
  60. package/libs/src/base/dto.ts +46 -0
  61. package/libs/src/base/index.ts +2 -0
  62. package/libs/src/base/resolver.ts +20 -0
  63. package/libs/src/database/database.module.ts +24 -22
  64. package/libs/src/database/database.repository.ts +47 -50
  65. package/libs/src/database/database.scheme.ts +2 -2
  66. package/libs/src/database/database.service.ts +61 -22
  67. package/libs/src/database/database.transaction.ts +62 -64
  68. package/libs/src/database/database.uniqueId.ts +39 -8
  69. package/libs/src/index.ts +3 -1
  70. package/libs/src/main.ts +8 -18
  71. package/libs/src/redis/index.ts +2 -0
  72. package/libs/src/redis/redis.interface.ts +34 -0
  73. package/libs/src/redis/redis.module.ts +30 -0
  74. package/libs/src/redis/redis.service.ts +137 -0
  75. package/libs/src/service/service.ts +48 -68
  76. package/libs/src/test/test.controller.ts +59 -0
  77. package/libs/src/test/test.module.ts +8 -4
  78. package/libs/src/test/test.redis.ts +19 -0
  79. package/libs/src/test/test.resolver.ts +10 -19
  80. 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
- handlePageFacet,
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
- public abstract mapData(data: any, isCreate: boolean): Promise<T>;
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 this._session;
33
+ return TransactionManager.getCurrentSession();
45
34
  }
46
35
 
47
36
  private get hasSession(): boolean {
48
- return !!this._session && !this._session.hasEnded;
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.mapData(data, true);
102
+ const payload: any = await this._mapData(data, true);
114
103
 
115
- payload._id = payload._id
116
- ? new Types.ObjectId(payload._id?.toString())
117
- : this.getObjectId();
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.mapData(data, false);
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
- protected async _updateMany(
191
- query: Partial<T>,
192
- model: Partial<T>
193
- ): Promise<void> {
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, createConnection } from "mongoose";
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
- public setConnection(companyId: string): Connection {
10
- this._connectionId = companyId
11
- ? companyId
12
- : process.env.mongodb_default_db_name;
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
- // Check if the connection already exists
15
- if (this.connections.has(companyId)) {
16
- return this.connections.get(companyId);
16
+ // Reuse existing pending/established connection
17
+ if (this.connections.has(dbName)) {
18
+ return this.connections.get(dbName);
17
19
  }
18
20
 
19
- // Create new connection
20
- const connection = createConnection(
21
- process.env.mongodb_url?.replace("{{db_name}}", companyId)
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
- this.connections.set(companyId, connection);
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
- public getConnection(): Connection {
30
- // Check if the connection already exists
31
- if (this.connections.has(this._connectionId)) {
32
- return this.connections.get(this._connectionId);
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
- return this.setConnection(this._connectionId);
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 { DatabaseService } from "./database.service";
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 _session: ClientSession;
12
- private _sessionName: string;
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 async getSession(): Promise<ClientSession> {
19
- return await this.dbSvc.getConnection().startSession();
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 async startTransaction(): Promise<TransactionSession> {
23
- this._session = await this.getSession();
19
+ public static getCurrentSession(): ClientSession | null {
20
+ const store = TransactionManager.storage.getStore();
21
+ return store?.session || null;
22
+ }
24
23
 
25
- this._session.startTransaction({
26
- readConcern: { level: "local" },
27
- writeConcern: { w: "majority", j: true },
28
- readPreference: "primary",
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 this._session as TransactionSession;
37
+ return store.session as TransactionSession;
31
38
  }
32
39
 
33
- public async commitTransaction(retries: number = 3): Promise<void> {
34
- // if (this._session && this._session.transaction.isActive) {
35
- // try {
36
- // await this._session.commitTransaction();
37
- // } catch (error) {
38
- // console.error("Transaction commit failed:", error);
39
- // await this.abortTransaction();
40
- // } finally {
41
- // await this._session.endSession();
42
- // }
43
- // }
44
- if (this._session && this._session.transaction.isActive) {
45
- let attempt = 0;
46
- while (attempt < retries) {
47
- try {
48
- await this._session.commitTransaction();
49
- return;
50
- } catch (error) {
51
- if (
52
- error instanceof MongoServerError &&
53
- error.message.includes("Write conflict during plan execution")
54
- ) {
55
- attempt++;
56
- if (attempt >= retries) {
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
- if (this._session && this._session.transaction.isActive) {
68
+ const store = TransactionManager.storage.getStore();
69
+ if (store?.session && store.session.transaction.isActive) {
76
70
  try {
77
- await this._session.abortTransaction();
78
- await this._session.endSession();
71
+ await store.session.abortTransaction();
79
72
  } catch (error) {
80
73
  } finally {
81
- await this._session.endSession();
74
+ await this.endTransaction();
82
75
  }
83
76
  }
84
77
  }
85
78
 
86
79
  public async endTransaction(): Promise<void> {
87
- if (this._session) {
80
+ const store = TransactionManager.storage.getStore();
81
+ if (store?.session) {
88
82
  try {
89
- await this._session.endSession();
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 this._sessionName;
90
+ return TransactionManager.storage.getStore()?.sessionName;
96
91
  }
97
92
 
98
93
  public set sessionName(name: string) {
99
- this._sessionName = name;
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
- constructor(
23
- @InjectModel(UniqueId.name)
24
- private model: SoftDeleteModel<UniqueIdDocument>
25
- ) {
26
- this.seed();
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
@@ -1,3 +1,5 @@
1
1
  export * from "./backup";
2
2
  export * from "./database";
3
- export * from "./service";
3
+ export * from "./service";
4
+ export * from "./base";
5
+ export * from "./redis";
package/libs/src/main.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import { NestFactory } from "@nestjs/core";
2
- import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
3
- import { ValidationPipe } from "@nestjs/common";
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(ApDbModule);
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 Database API")
27
- .setDescription("API documentation for Zync Nest Database")
16
+ .setTitle("Zync Nest Data Module API")
17
+ .setDescription("API documentation for Zync Nest Data Module")
28
18
  .setVersion("1.0.23")
29
- .addTag("Database", "Database endpoints for Zync Nest Database")
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 Database API",
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 Database server is running on: http://localhost:${port}`
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,2 @@
1
+ export * from "./redis.service";
2
+ export * from "./redis.module";
@@ -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
+ }