@nxgt/shared-mongo 1.0.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.
Files changed (169) hide show
  1. package/README.md +15 -0
  2. package/dist/audit/audit.model.d.ts +209 -0
  3. package/dist/audit/audit.model.d.ts.map +1 -0
  4. package/dist/audit/audit.service.d.ts +17 -0
  5. package/dist/audit/audit.service.d.ts.map +1 -0
  6. package/dist/audit/audit.types.d.ts +29 -0
  7. package/dist/audit/audit.types.d.ts.map +1 -0
  8. package/dist/audit/audit.utils.d.ts +50 -0
  9. package/dist/audit/audit.utils.d.ts.map +1 -0
  10. package/dist/audit/index.d.ts +5 -0
  11. package/dist/audit/index.d.ts.map +1 -0
  12. package/dist/filters/filter.utils.d.ts +112 -0
  13. package/dist/filters/filter.utils.d.ts.map +1 -0
  14. package/dist/filters/index.d.ts +16 -0
  15. package/dist/filters/index.d.ts.map +1 -0
  16. package/dist/filters/index.js +199 -0
  17. package/dist/filters/index.js.map +10 -0
  18. package/dist/index.d.ts +14 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +1847 -0
  21. package/dist/index.js.map +70 -0
  22. package/dist/integrity/index.d.ts +3 -0
  23. package/dist/integrity/index.d.ts.map +1 -0
  24. package/dist/integrity/integrity.registry.d.ts +38 -0
  25. package/dist/integrity/integrity.registry.d.ts.map +1 -0
  26. package/dist/integrity/integrity.types.d.ts +26 -0
  27. package/dist/integrity/integrity.types.d.ts.map +1 -0
  28. package/dist/migrations/cli.d.ts +28 -0
  29. package/dist/migrations/cli.d.ts.map +1 -0
  30. package/dist/migrations/disconnect.d.ts +8 -0
  31. package/dist/migrations/disconnect.d.ts.map +1 -0
  32. package/dist/migrations/index.d.ts +6 -0
  33. package/dist/migrations/index.d.ts.map +1 -0
  34. package/dist/migrations/migrate.d.ts +47 -0
  35. package/dist/migrations/migrate.d.ts.map +1 -0
  36. package/dist/migrations/migration.model.d.ts +7 -0
  37. package/dist/migrations/migration.model.d.ts.map +1 -0
  38. package/dist/migrations/migration.runner.d.ts +47 -0
  39. package/dist/migrations/migration.runner.d.ts.map +1 -0
  40. package/dist/migrations/migration.types.d.ts +48 -0
  41. package/dist/migrations/migration.types.d.ts.map +1 -0
  42. package/dist/migrations/migration.utils.d.ts +40 -0
  43. package/dist/migrations/migration.utils.d.ts.map +1 -0
  44. package/dist/models/index.d.ts +3 -0
  45. package/dist/models/index.d.ts.map +1 -0
  46. package/dist/models/pagination.d.ts +34 -0
  47. package/dist/models/pagination.d.ts.map +1 -0
  48. package/dist/models/postal-address.model.d.ts +64 -0
  49. package/dist/models/postal-address.model.d.ts.map +1 -0
  50. package/dist/mongoose.d.ts +3 -0
  51. package/dist/mongoose.d.ts.map +1 -0
  52. package/dist/plugins/consts.d.ts +6 -0
  53. package/dist/plugins/consts.d.ts.map +1 -0
  54. package/dist/plugins/errors.d.ts +3 -0
  55. package/dist/plugins/errors.d.ts.map +1 -0
  56. package/dist/plugins/index.d.ts +15 -0
  57. package/dist/plugins/index.d.ts.map +1 -0
  58. package/dist/plugins/pagination/index.d.ts +4 -0
  59. package/dist/plugins/pagination/index.d.ts.map +1 -0
  60. package/dist/plugins/pagination/types.d.ts +61 -0
  61. package/dist/plugins/pagination/types.d.ts.map +1 -0
  62. package/dist/plugins/pagination/utils.d.ts +24 -0
  63. package/dist/plugins/pagination/utils.d.ts.map +1 -0
  64. package/dist/plugins/shared/index.d.ts +3 -0
  65. package/dist/plugins/shared/index.d.ts.map +1 -0
  66. package/dist/plugins/shared/shared.d.ts +3 -0
  67. package/dist/plugins/shared/shared.d.ts.map +1 -0
  68. package/dist/plugins/soft-delete/index.d.ts +3 -0
  69. package/dist/plugins/soft-delete/index.d.ts.map +1 -0
  70. package/dist/plugins/soft-delete/soft-delete.d.ts +3 -0
  71. package/dist/plugins/soft-delete/soft-delete.d.ts.map +1 -0
  72. package/dist/services/crud.service.d.ts +88 -0
  73. package/dist/services/crud.service.d.ts.map +1 -0
  74. package/dist/services/index.d.ts +2 -0
  75. package/dist/services/index.d.ts.map +1 -0
  76. package/dist/types/index.d.ts +4 -0
  77. package/dist/types/index.d.ts.map +1 -0
  78. package/dist/types/pagination.d.ts +72 -0
  79. package/dist/types/shared.d.ts +49 -0
  80. package/dist/types/soft-delete.d.ts +627 -0
  81. package/dist/utils/clear-database.d.ts +24 -0
  82. package/dist/utils/clear-database.d.ts.map +1 -0
  83. package/dist/utils/error.utils.d.ts +4 -0
  84. package/dist/utils/error.utils.d.ts.map +1 -0
  85. package/dist/utils/filter.utils.d.ts +40 -0
  86. package/dist/utils/filter.utils.d.ts.map +1 -0
  87. package/dist/utils/helpers.d.ts +72 -0
  88. package/dist/utils/helpers.d.ts.map +1 -0
  89. package/dist/utils/index.d.ts +12 -0
  90. package/dist/utils/index.d.ts.map +1 -0
  91. package/dist/utils/mappers.d.ts +4 -0
  92. package/dist/utils/mappers.d.ts.map +1 -0
  93. package/dist/utils/models.d.ts +3 -0
  94. package/dist/utils/models.d.ts.map +1 -0
  95. package/dist/utils/mongo-utils.d.ts +45 -0
  96. package/dist/utils/mongo-utils.d.ts.map +1 -0
  97. package/dist/utils/object-id.utils.d.ts +16 -0
  98. package/dist/utils/object-id.utils.d.ts.map +1 -0
  99. package/dist/utils/pagination.utils.d.ts +6 -0
  100. package/dist/utils/pagination.utils.d.ts.map +1 -0
  101. package/dist/utils/schema.d.ts +2 -0
  102. package/dist/utils/schema.d.ts.map +1 -0
  103. package/dist/utils/sort.utils.d.ts +12 -0
  104. package/dist/utils/sort.utils.d.ts.map +1 -0
  105. package/dist/validation/index.d.ts +2 -0
  106. package/dist/validation/index.d.ts.map +1 -0
  107. package/dist/validation/validation.types.d.ts +36 -0
  108. package/dist/validation/validation.types.d.ts.map +1 -0
  109. package/dist/validation/validation.utils.d.ts +6 -0
  110. package/dist/validation/validation.utils.d.ts.map +1 -0
  111. package/dist/validation/validators/email.validator.d.ts +8 -0
  112. package/dist/validation/validators/email.validator.d.ts.map +1 -0
  113. package/dist/validation/validators/future.validator.d.ts +8 -0
  114. package/dist/validation/validators/future.validator.d.ts.map +1 -0
  115. package/dist/validation/validators/guid.validator.d.ts +6 -0
  116. package/dist/validation/validators/guid.validator.d.ts.map +1 -0
  117. package/dist/validation/validators/index.d.ts +30 -0
  118. package/dist/validation/validators/index.d.ts.map +1 -0
  119. package/dist/validation/validators/ip.validator.d.ts +6 -0
  120. package/dist/validation/validators/ip.validator.d.ts.map +1 -0
  121. package/dist/validation/validators/ipv4.validator.d.ts +6 -0
  122. package/dist/validation/validators/ipv4.validator.d.ts.map +1 -0
  123. package/dist/validation/validators/ipv6.validator.d.ts +6 -0
  124. package/dist/validation/validators/ipv6.validator.d.ts.map +1 -0
  125. package/dist/validation/validators/mac.validator.d.ts +6 -0
  126. package/dist/validation/validators/mac.validator.d.ts.map +1 -0
  127. package/dist/validation/validators/max-length.validator.d.ts +6 -0
  128. package/dist/validation/validators/max-length.validator.d.ts.map +1 -0
  129. package/dist/validation/validators/max.validator.d.ts +6 -0
  130. package/dist/validation/validators/max.validator.d.ts.map +1 -0
  131. package/dist/validation/validators/min-length.validator.d.ts +6 -0
  132. package/dist/validation/validators/min-length.validator.d.ts.map +1 -0
  133. package/dist/validation/validators/min.validator.d.ts +6 -0
  134. package/dist/validation/validators/min.validator.d.ts.map +1 -0
  135. package/dist/validation/validators/negative.validator.d.ts +6 -0
  136. package/dist/validation/validators/negative.validator.d.ts.map +1 -0
  137. package/dist/validation/validators/not-blank.validator.d.ts +6 -0
  138. package/dist/validation/validators/not-blank.validator.d.ts.map +1 -0
  139. package/dist/validation/validators/not-empty.validator.d.ts +6 -0
  140. package/dist/validation/validators/not-empty.validator.d.ts.map +1 -0
  141. package/dist/validation/validators/object-id.validator.d.ts +6 -0
  142. package/dist/validation/validators/object-id.validator.d.ts.map +1 -0
  143. package/dist/validation/validators/past.validator.d.ts +8 -0
  144. package/dist/validation/validators/past.validator.d.ts.map +1 -0
  145. package/dist/validation/validators/pattern.validator.d.ts +6 -0
  146. package/dist/validation/validators/pattern.validator.d.ts.map +1 -0
  147. package/dist/validation/validators/phone.validator.d.ts +6 -0
  148. package/dist/validation/validators/phone.validator.d.ts.map +1 -0
  149. package/dist/validation/validators/positive.validator.d.ts +6 -0
  150. package/dist/validation/validators/positive.validator.d.ts.map +1 -0
  151. package/dist/validation/validators/range.validator.d.ts +6 -0
  152. package/dist/validation/validators/range.validator.d.ts.map +1 -0
  153. package/dist/validation/validators/required.validator.d.ts +6 -0
  154. package/dist/validation/validators/required.validator.d.ts.map +1 -0
  155. package/dist/validation/validators/size.validator.d.ts +6 -0
  156. package/dist/validation/validators/size.validator.d.ts.map +1 -0
  157. package/dist/validation/validators/ssn.validator.d.ts +6 -0
  158. package/dist/validation/validators/ssn.validator.d.ts.map +1 -0
  159. package/dist/validation/validators/street.validator.d.ts +6 -0
  160. package/dist/validation/validators/street.validator.d.ts.map +1 -0
  161. package/dist/validation/validators/time.validator.d.ts +6 -0
  162. package/dist/validation/validators/time.validator.d.ts.map +1 -0
  163. package/dist/validation/validators/url.validator.d.ts +8 -0
  164. package/dist/validation/validators/url.validator.d.ts.map +1 -0
  165. package/dist/validation/validators/uuid.validator.d.ts +6 -0
  166. package/dist/validation/validators/uuid.validator.d.ts.map +1 -0
  167. package/dist/validation/validators/zip-code.validator.d.ts +6 -0
  168. package/dist/validation/validators/zip-code.validator.d.ts.map +1 -0
  169. package/package.json +68 -0
package/dist/index.js ADDED
@@ -0,0 +1,1847 @@
1
+ import { createRequire } from "node:module";
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
17
+
18
+ // src/types/pagination.d.ts
19
+ import"mongoose";
20
+
21
+ // src/index.ts
22
+ export * from "async-mutex";
23
+ export * from "mongoose";
24
+
25
+ // src/audit/audit.model.ts
26
+ import { logger as logger3 } from "@nxgt/shared-logging";
27
+ import { redis } from "bun";
28
+ import { model, Schema } from "mongoose";
29
+
30
+ // src/plugins/pagination/utils.ts
31
+ import { CustomException } from "@nxgt/shared-exceptions";
32
+ var MAX_SIZE = 100;
33
+ function applyPagination(schema) {
34
+ schema.static("paginate", async function() {
35
+ const options = arguments?.[0] ?? {};
36
+ const { first, last, before, after, extraFilter } = options;
37
+ let filter = options.filter ?? {};
38
+ const deleted = options.deleted ?? "";
39
+ const countDocuments = (f) => this[`countDocuments${deleted}`]?.(f) ?? this.countDocuments(f);
40
+ if (extraFilter) {
41
+ filter = filter ? { $and: [filter, extraFilter] } : extraFilter;
42
+ }
43
+ if (first && last) {
44
+ throw CustomException.badRequest({
45
+ message: "errors.could-not-use-first-and-last-together"
46
+ });
47
+ }
48
+ if (after) {
49
+ filter = filter ? { $and: [filter, { _id: { $gt: after } }] } : { _id: { $gt: after } };
50
+ }
51
+ if (before) {
52
+ filter = filter ? { $and: [filter, { _id: { $lt: before } }] } : { _id: { $lt: before } };
53
+ }
54
+ const limit = Math.min(first ?? last ?? MAX_SIZE, MAX_SIZE);
55
+ const sort = { _id: last || before ? -1 : 1 };
56
+ let query = (this[`find${deleted}`]?.(filter, arguments?.[1], arguments?.[2]) ?? this.find(filter, arguments?.[1], arguments?.[2])).sort(sort);
57
+ query = query.limit(limit + 1);
58
+ const result = await query.exec();
59
+ const hasExtraDoc = result.length > limit;
60
+ const actualResults = hasExtraDoc ? result.slice(0, limit) : result;
61
+ const total = await countDocuments(filter);
62
+ const edges = actualResults.map((item) => ({
63
+ node: item,
64
+ cursor: item.id
65
+ }));
66
+ const pageInfo = {
67
+ hasNextPage: hasExtraDoc,
68
+ startCursor: edges?.[0]?.cursor,
69
+ endCursor: edges?.[edges.length - 1]?.cursor
70
+ };
71
+ return { edges, pageInfo, totalCount: total };
72
+ });
73
+ schema.static("paginateList", function() {
74
+ const docs = arguments?.[0] ?? [];
75
+ const options = arguments?.[1] ?? {};
76
+ return paginateListData(docs, options);
77
+ });
78
+ schema.static("paginateOffset", async function() {
79
+ const options = arguments?.[0] ?? {};
80
+ const filter = options.filter ?? {};
81
+ const sort = options.sort ?? {};
82
+ const populate = options.populate ?? [];
83
+ const deleted = options.deleted ?? "";
84
+ const count = await (this[`countDocuments${deleted}`]?.(filter) ?? this.countDocuments(filter));
85
+ const { size, page, totalPages, skip } = offsetWindow(options, count);
86
+ const docs = await (this[`find${deleted}`]?.(filter, arguments?.[1], arguments?.[2]) ?? this.find(filter, arguments?.[1], arguments?.[2])).populate(populate).sort(sort).skip(skip).limit(size).exec();
87
+ return {
88
+ data: docs,
89
+ metadata: {
90
+ page,
91
+ size,
92
+ totalElements: count,
93
+ totalPages,
94
+ nbOfElements: docs.length,
95
+ hasPreviousPage: page > 1,
96
+ hasNextPage: page < totalPages
97
+ }
98
+ };
99
+ });
100
+ schema.static("paginateListOffset", async function() {
101
+ const docs = arguments?.[0] ?? [];
102
+ const options = arguments?.[1] ?? {};
103
+ const count = docs.length;
104
+ const { size, page, totalPages, skip } = offsetWindow(options, count);
105
+ return {
106
+ data: docs.slice(skip, skip + size),
107
+ metadata: {
108
+ page,
109
+ size,
110
+ totalElements: count,
111
+ totalPages,
112
+ nbOfElements: docs.length,
113
+ hasPreviousPage: page > 1,
114
+ hasNextPage: page < totalPages
115
+ }
116
+ };
117
+ });
118
+ schema.static("cursorPaginate", async function() {
119
+ const options = arguments?.[0] ?? {};
120
+ const filter = options.filter ?? {};
121
+ const populate = options.populate ?? [];
122
+ const deleted = options.deleted ?? "";
123
+ const { after, before } = options;
124
+ const first = options.first && options.first > 0 ? options.first : undefined;
125
+ const last = options.last && options.last > 0 ? options.last : undefined;
126
+ if (first && last) {
127
+ throw CustomException.badRequest({
128
+ message: "errors.could-not-use-first-and-last-together"
129
+ });
130
+ }
131
+ const cursorFilter = filter;
132
+ if (after)
133
+ cursorFilter._id = { $gt: after };
134
+ if (before)
135
+ cursorFilter._id = { $lt: before };
136
+ const limit = Math.min(first ?? last ?? MAX_SIZE, MAX_SIZE);
137
+ const querySort = { _id: last || before ? -1 : 1 };
138
+ const query = (this[`find${deleted}`]?.(cursorFilter, arguments?.[1], arguments?.[2]) ?? this.find(cursorFilter, arguments?.[1], arguments?.[2])).populate(populate).sort(querySort);
139
+ query.limit(Math.max(limit ?? 0, 1) + 1);
140
+ const docs = await query.exec();
141
+ const hasExtraDoc = docs.length > limit;
142
+ const resultDocs = hasExtraDoc ? docs.slice(0, limit) : docs;
143
+ if (last) {
144
+ resultDocs.reverse();
145
+ }
146
+ const totalElements = await (this[`countDocuments${deleted}`]?.(filter) ?? this.countDocuments(filter));
147
+ return {
148
+ data: resultDocs,
149
+ metadata: {
150
+ startCursor: resultDocs.length > 0 ? resultDocs[0]._id.toString() : null,
151
+ endCursor: resultDocs.length > 0 ? resultDocs[resultDocs.length - 1]._id.toString() : null,
152
+ hasNextPage: hasExtraDoc,
153
+ totalElements
154
+ }
155
+ };
156
+ });
157
+ }
158
+ function offsetWindow(options, count) {
159
+ const size = Math.min(Math.max(options.size ?? 20, 1), MAX_SIZE);
160
+ const page = Math.max(options.page ?? 1, 1);
161
+ return {
162
+ size,
163
+ page,
164
+ totalPages: Math.floor((count - 1) / size) + 1,
165
+ skip: (page - 1) * size
166
+ };
167
+ }
168
+ function paginateListData(list, options) {
169
+ const { first, last, before, after } = options;
170
+ if (first && last) {
171
+ throw CustomException.badRequest({
172
+ message: "errors.could-not-use-first-and-last-together"
173
+ });
174
+ }
175
+ const isBackward = last || before;
176
+ const sortedData = list.sort((a, b) => {
177
+ if (a._id.toString() < b._id.toString())
178
+ return isBackward ? 1 : -1;
179
+ if (a._id.toString() > b._id.toString())
180
+ return isBackward ? -1 : 1;
181
+ return 0;
182
+ });
183
+ const total = sortedData.length;
184
+ let filteredData = sortedData;
185
+ if (after || before) {
186
+ const index = sortedData.findIndex((item) => item.id === (after ?? before));
187
+ filteredData = index !== -1 ? sortedData.slice(index + 1) : sortedData;
188
+ }
189
+ const limit = Math.min(first ?? last ?? MAX_SIZE, MAX_SIZE);
190
+ const hasExtraDoc = filteredData.length > limit;
191
+ const resultData = filteredData.slice(0, limit);
192
+ const edges = resultData.map((item) => ({
193
+ node: item,
194
+ cursor: item.id
195
+ }));
196
+ const pageInfo = {
197
+ hasNextPage: hasExtraDoc,
198
+ startCursor: edges?.[0]?.cursor,
199
+ endCursor: edges?.[edges.length - 1]?.cursor
200
+ };
201
+ return { edges, pageInfo, totalCount: total };
202
+ }
203
+
204
+ // src/plugins/pagination/types.ts
205
+ class OffsetPaginationOptions {
206
+ constructor() {}
207
+ }
208
+
209
+ class NestedOffsetPaginationOptions {
210
+ constructor() {}
211
+ }
212
+
213
+ class CursorPaginationOptions {
214
+ constructor() {}
215
+ }
216
+
217
+ // src/plugins/pagination/index.ts
218
+ var pagination = (schema) => {
219
+ applyPagination(schema);
220
+ };
221
+
222
+ // src/audit/audit.types.ts
223
+ var ChangeOperationType;
224
+ ((ChangeOperationType2) => {
225
+ ChangeOperationType2["ADD"] = "add";
226
+ ChangeOperationType2["REPLACE"] = "replace";
227
+ ChangeOperationType2["REMOVE"] = "remove";
228
+ })(ChangeOperationType ||= {});
229
+ var CommitType;
230
+ ((CommitType2) => {
231
+ CommitType2["INITIAL"] = "INITIAL";
232
+ CommitType2["UPDATE"] = "UPDATE";
233
+ CommitType2["TERMINAL"] = "TERMINAL";
234
+ })(CommitType ||= {});
235
+
236
+ // src/audit/audit.utils.ts
237
+ import { delay } from "@nxgt/shared/helpers";
238
+ import { createLogger } from "@nxgt/shared-logging";
239
+ import { Mutex } from "async-mutex";
240
+ import { diff } from "just-diff";
241
+ import { omit as omit2 } from "lodash";
242
+
243
+ // src/mongoose.ts
244
+ import mongoose from "mongoose";
245
+
246
+ // src/utils/clear-database.ts
247
+ async function clearDatabase(connection = mongoose.connection) {
248
+ const database = connection.name;
249
+ if (Bun.env.NODE_ENV !== "test" || !database.endsWith("-test")) {
250
+ throw new Error(`clearDatabase refused to run: NODE_ENV="${Bun.env.NODE_ENV}", database="${database}". ` + 'It only runs with NODE_ENV=test against a database whose name ends in "-test". ' + "A test run reaching a non-test database usually means MONGODB_URI is set in the " + "shell, or the test env file was not passed.");
251
+ }
252
+ const collections = await connection.listCollections();
253
+ await Promise.all(collections.filter((collection) => collection.type !== "view" && !collection.name.includes("system")).map((collection) => connection.collection(collection.name).deleteMany({})));
254
+ }
255
+ // src/utils/error.utils.ts
256
+ import { CustomException as CustomException2 } from "@nxgt/shared-exceptions";
257
+ import mongoose2 from "mongoose";
258
+ function castError(error) {
259
+ let props = {};
260
+ if (error instanceof CustomException2) {
261
+ return error;
262
+ }
263
+ switch (true) {
264
+ case error instanceof mongoose2.Error.CastError:
265
+ props = {
266
+ message: "errors.cast-failed",
267
+ options: { field: error.path }
268
+ };
269
+ break;
270
+ case error instanceof mongoose2.Error.VersionError:
271
+ props = {
272
+ message: "errors.optimistic-lock-failed",
273
+ debugMessage: error.message
274
+ };
275
+ break;
276
+ case error instanceof mongoose2.Error.DocumentNotFoundError:
277
+ props = {
278
+ message: "errors.not-found"
279
+ };
280
+ break;
281
+ case error instanceof mongoose2.Error.MongooseServerSelectionError:
282
+ props = {
283
+ message: "errors.server-connection-failed"
284
+ };
285
+ break;
286
+ case error instanceof mongoose2.Error.ValidationError:
287
+ props = {
288
+ message: Object.values(error.errors).map((e) => e.message).join(" ")
289
+ };
290
+ break;
291
+ default:
292
+ props = {
293
+ message: "errors.something-went-wrong"
294
+ };
295
+ }
296
+ return CustomException2.badRequest({
297
+ message: props.message || "",
298
+ options: props.options,
299
+ debugMessage: error.message
300
+ });
301
+ }
302
+ // src/utils/filter.utils.ts
303
+ function buildDateRangeFilter(dateRange) {
304
+ if (!dateRange) {
305
+ return;
306
+ }
307
+ const filter = {};
308
+ if (dateRange.from) {
309
+ filter.$gte = new Date(dateRange.from);
310
+ }
311
+ if (dateRange.to) {
312
+ filter.$lte = new Date(dateRange.to);
313
+ }
314
+ return Object.keys(filter).length > 0 ? filter : undefined;
315
+ }
316
+ function buildArrayFilter(arrayFilter) {
317
+ if (!arrayFilter?.values) {
318
+ return;
319
+ }
320
+ return arrayFilter.operator === "and" ? { $all: arrayFilter.values } : arrayFilter.values;
321
+ }
322
+ function addFilterCondition(query, field, condition) {
323
+ if (condition) {
324
+ query[field] = condition;
325
+ }
326
+ }
327
+ function escapeRegex(value) {
328
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
329
+ }
330
+ function buildRegexFilter(value) {
331
+ if (value) {
332
+ return { $regex: escapeRegex(value), $options: "i" };
333
+ }
334
+ return;
335
+ }
336
+ // src/utils/helpers.ts
337
+ var exports_helpers = {};
338
+ __export(exports_helpers, {
339
+ buildListStringPatch: () => buildListStringPatch,
340
+ buildListStringPatchUpdate: () => buildListStringPatchUpdate,
341
+ checkVersion: () => checkVersion,
342
+ patchListObjectId: () => patchListObjectId,
343
+ patchListString: () => patchListString,
344
+ resolveListStringPatch: () => resolveListStringPatch,
345
+ safeCreateView: () => safeCreateView
346
+ });
347
+ import { CustomException as CustomException3 } from "@nxgt/shared-exceptions";
348
+ import { logger } from "@nxgt/shared-logging";
349
+ import { isNil } from "lodash";
350
+ import mongoose4 from "mongoose";
351
+
352
+ // src/utils/object-id.utils.ts
353
+ import mongoose3, { Types } from "mongoose";
354
+ function objectIdString() {
355
+ return new mongoose3.mongo.ObjectId().toHexString();
356
+ }
357
+ function toObjectId(id) {
358
+ return new Types.ObjectId(id);
359
+ }
360
+ function toObjectIds(ids) {
361
+ return ids.map(toObjectId);
362
+ }
363
+ function isValidObjectID(id) {
364
+ return Types.ObjectId.isValid(id);
365
+ }
366
+ function validObjectIds(ids) {
367
+ return ids?.filter(isValidObjectID);
368
+ }
369
+ function objectIdFromString(id) {
370
+ return toObjectId(id);
371
+ }
372
+
373
+ // src/utils/helpers.ts
374
+ async function isView(name) {
375
+ const db = mongoose4.connection.db;
376
+ if (!db)
377
+ return false;
378
+ const [existing] = await db.listCollections({ name }).toArray();
379
+ return existing?.type === "view";
380
+ }
381
+ async function safeCreateView(model, viewModel, pipeline = [], attempts = 3) {
382
+ try {
383
+ await model.createCollection();
384
+ } catch (error) {
385
+ logger.error(`Error creating collection for model ${model.modelName}:`, error);
386
+ }
387
+ const viewName = viewModel.collection.name;
388
+ for (let attempt = 1;attempt <= attempts; attempt++) {
389
+ try {
390
+ await mongoose4.connection.dropCollection(viewName);
391
+ logger.info(`Dropped existing view for model ${viewModel.modelName}`);
392
+ } catch (error) {
393
+ logger.debug(`No existing namespace to drop for model ${viewModel.modelName}: ${error}`);
394
+ }
395
+ try {
396
+ await viewModel.createCollection({
397
+ viewOn: model.collection.name,
398
+ pipeline
399
+ });
400
+ } catch (error) {
401
+ logger.error(`Error creating view for model ${viewModel.modelName}:`, error);
402
+ }
403
+ if (await isView(viewName))
404
+ return;
405
+ logger.warn(`${viewName} is not a view after attempt ${attempt}/${attempts} — retrying`);
406
+ if (attempt < attempts) {
407
+ await new Promise((resolve) => setTimeout(resolve, attempt * 50));
408
+ }
409
+ }
410
+ logger.error(`Failed to create view ${viewName} for model ${viewModel.modelName}: it exists as a plain collection, so every query through it will come back empty.`);
411
+ }
412
+ function patchListString(list = [], patch) {
413
+ if (!patch) {
414
+ return list;
415
+ }
416
+ const add = patch.add ?? [];
417
+ const remove = patch.remove ?? [];
418
+ const replace = patch.replace ?? [];
419
+ let result = [...list, ...add];
420
+ result = result.filter((item) => !remove.includes(item));
421
+ if (replace.length) {
422
+ result = replace;
423
+ }
424
+ return Array.from(new Set(result));
425
+ }
426
+ function patchListObjectId(list = [], patch) {
427
+ const values = list.map((item) => item instanceof mongoose4.Types.ObjectId ? item.toHexString() : item._id.toHexString());
428
+ return patchListString(values, patch).filter(isValidObjectID).map((id) => new mongoose4.Types.ObjectId(id));
429
+ }
430
+ async function resolveListStringPatch(current, model, value, filter = {}) {
431
+ if (!value?.replace?.length && !value?.add?.length && !value?.remove?.length) {
432
+ return;
433
+ }
434
+ const idsOf = async (ids) => ids?.length ? (await model.find({ _id: ids, ...filter }).exec()).map((item) => String(item._id)) : [];
435
+ if (value.replace?.length) {
436
+ return idsOf(value.replace);
437
+ }
438
+ const held = (Array.isArray(current) ? current : []).map((item) => String(item?._id ?? item));
439
+ const removed = new Set(value.remove ?? []);
440
+ return [
441
+ ...new Set([
442
+ ...held.filter((id) => !removed.has(id)),
443
+ ...await idsOf(value.add)
444
+ ])
445
+ ];
446
+ }
447
+ async function buildListStringPatch(_source, path, model, value, filter = {}) {
448
+ const add = value?.add?.length ? (await model.find({
449
+ _id: value.add,
450
+ ...filter
451
+ }).exec()).map((item) => item._id) : [];
452
+ const remove = value?.remove?.length ? (await model.find({
453
+ _id: value.remove,
454
+ ...filter
455
+ }).exec()).map((item) => item._id) : [];
456
+ const replace = value?.replace?.length ? (await model.find({
457
+ _id: value.replace,
458
+ ...filter
459
+ }).exec()).map((item) => item._id) : [];
460
+ return {
461
+ ...value?.add?.length && {
462
+ $addToSet: {
463
+ [path]: {
464
+ $each: add
465
+ }
466
+ }
467
+ },
468
+ ...value?.remove?.length && {
469
+ $pullAll: {
470
+ [path]: remove
471
+ }
472
+ },
473
+ ...value?.replace?.length && {
474
+ [path]: replace
475
+ }
476
+ };
477
+ }
478
+ async function buildListStringPatchUpdate(path, value) {
479
+ const add = value?.add ?? [];
480
+ const remove = value?.remove ?? [];
481
+ const replace = value?.replace ?? [];
482
+ return {
483
+ ...add.length && {
484
+ $addToSet: {
485
+ [path]: {
486
+ $each: add
487
+ }
488
+ }
489
+ },
490
+ ...remove.length && {
491
+ $pullAll: {
492
+ [path]: remove
493
+ }
494
+ },
495
+ ...replace.length && {
496
+ [path]: replace
497
+ }
498
+ };
499
+ }
500
+ function checkVersion(modelVersion, version) {
501
+ if (isNil(version)) {
502
+ return;
503
+ }
504
+ if (modelVersion !== version) {
505
+ throw CustomException3.badRequest({
506
+ message: "errors.optimistic-lock-failed"
507
+ });
508
+ }
509
+ }
510
+ // src/utils/mappers.ts
511
+ var exports_mappers = {};
512
+ __export(exports_mappers, {
513
+ toObject: () => toObject,
514
+ toObjects: () => toObjects
515
+ });
516
+ import { omit } from "lodash";
517
+ var toObject = (doc, ...omitFields) => {
518
+ if (!doc)
519
+ return null;
520
+ return {
521
+ ...omit(doc.toObject({ getters: true, virtuals: true, versionKey: true }), [
522
+ "_id",
523
+ ...omitFields
524
+ ])
525
+ };
526
+ };
527
+ var toObjects = (docs, ...omitFields) => docs.map((doc) => toObject(doc, ...omitFields));
528
+ // src/plugins/index.ts
529
+ import autopopulate from "mongoose-autopopulate";
530
+ import leanVirtuals from "mongoose-lean-virtuals";
531
+
532
+ // src/plugins/errors.ts
533
+ import { CustomException as CustomException4 } from "@nxgt/shared-exceptions";
534
+ var errors = (schema) => {
535
+ schema.post([
536
+ "save",
537
+ "deleteOne",
538
+ "findOneAndDelete",
539
+ "updateOne",
540
+ "findOneAndUpdate",
541
+ "replaceOne",
542
+ "findOneAndReplace",
543
+ "findOne",
544
+ "countDocuments"
545
+ ], (error, _, next) => {
546
+ if (error) {
547
+ if (error.name === "MongoServerError" && error.code === 11000) {
548
+ next(CustomException4.badRequest({
549
+ message: "errors.duplicate-key",
550
+ debugMessage: error.message
551
+ }));
552
+ } else {
553
+ next(castError(error));
554
+ }
555
+ } else {
556
+ next();
557
+ }
558
+ });
559
+ };
560
+
561
+ // src/plugins/shared/shared.ts
562
+ import { translate } from "@nxgt/i18n";
563
+ import { CustomException as CustomException5, ErrorCode } from "@nxgt/shared-exceptions";
564
+ import { has } from "lodash";
565
+ import mongoose5 from "mongoose";
566
+ function applySharedOperations(schema) {
567
+ schema.static("ensureExists", async function() {
568
+ const result = await mongoose5.Model.exists.apply(this, [arguments?.[0] ?? {}]).exec();
569
+ if (!result) {
570
+ const localeKey = `${this.collection.collectionName}.errors.not-found`;
571
+ throw CustomException5.notFound({
572
+ message: arguments?.[1]?.message ?? (translate(localeKey) === localeKey ? "errors.not-found" : localeKey),
573
+ options: arguments?.[1]?.options
574
+ });
575
+ }
576
+ });
577
+ schema.static("requireById", async function() {
578
+ try {
579
+ const doc = await mongoose5.Model.findById.apply(this, [arguments?.[0] ?? {}]).exec();
580
+ if (!doc) {
581
+ const localeKey = `${this.collection.collectionName}.errors.not-found`;
582
+ throw CustomException5.notFound({
583
+ message: arguments?.[1]?.message ?? (translate(localeKey) === localeKey ? "errors.not-found" : localeKey),
584
+ options: arguments?.[1]?.options
585
+ });
586
+ }
587
+ return doc;
588
+ } catch (error) {
589
+ if (error instanceof CustomException5) {
590
+ throw error;
591
+ }
592
+ throw CustomException5.from({
593
+ message: arguments?.[1]?.message ?? error?.message,
594
+ code: arguments?.[1]?.code ?? ErrorCode.BadRequest,
595
+ debugMessage: translate(error.message)
596
+ });
597
+ }
598
+ });
599
+ if (schema.options.versionKey) {
600
+ schema.pre(["findOneAndUpdate"], async function() {
601
+ const update = this.getUpdate();
602
+ if (!update) {
603
+ return;
604
+ }
605
+ if (has(update, "version")) {
606
+ delete update.version;
607
+ }
608
+ const keys = ["$set", "$setOnInsert"];
609
+ for (const key of keys) {
610
+ if (update[key] != null && update[key].version != null) {
611
+ delete update[key].version;
612
+ if (Object.keys(update[key]).length === 0) {
613
+ delete update[key];
614
+ }
615
+ }
616
+ }
617
+ update.$inc = update.$inc || {};
618
+ update.$inc.version = 1;
619
+ });
620
+ }
621
+ }
622
+
623
+ // src/plugins/shared/index.ts
624
+ function shared(schema) {
625
+ applySharedOperations(schema);
626
+ }
627
+
628
+ // src/plugins/consts.ts
629
+ var PLUGIN_OPTIONS = {
630
+ autopopulate: { maxDepth: 1 }
631
+ };
632
+
633
+ // src/plugins/index.ts
634
+ var MONGOOSE_PLUGINS = {
635
+ shared,
636
+ errors,
637
+ pagination,
638
+ leanVirtuals,
639
+ autopopulate
640
+ };
641
+ function applyPlugins(schema, plugins) {
642
+ plugins.forEach((plugin) => {
643
+ schema.plugin(plugin);
644
+ });
645
+ }
646
+
647
+ // src/utils/mongo-utils.ts
648
+ var MONGO_UTILS = {
649
+ applyPlugins,
650
+ castError,
651
+ auditChanges,
652
+ ...exports_mappers,
653
+ ...exports_helpers,
654
+ timestamps: { createdAt: "createdDate", updatedAt: "lastModifiedDate" },
655
+ coreOptions: {
656
+ toObject: { virtuals: true, versionKey: true },
657
+ toJSON: { virtuals: true, versionKey: true },
658
+ timestamps: { createdAt: "createdDate", updatedAt: "lastModifiedDate" },
659
+ versionKey: "version",
660
+ optimisticConcurrency: true
661
+ },
662
+ auditPipeline: [
663
+ {
664
+ $match: {
665
+ operationType: { $in: ["insert", "update", "replace", "delete"] }
666
+ }
667
+ }
668
+ ]
669
+ };
670
+ // src/utils/pagination.utils.ts
671
+ function paginateList(docs, options) {
672
+ return paginateListData(docs, options);
673
+ }
674
+ // src/utils/sort.utils.ts
675
+ import { buildSort, SortDirection } from "@nxgt/shared/helpers";
676
+ // src/audit/audit.utils.ts
677
+ var mutex = new Mutex;
678
+ var logger2 = createLogger({ name: "audit" });
679
+ function auditChanges(changeStream, options) {
680
+ options.omitFields = options.omitFields ?? [
681
+ "_id",
682
+ "id",
683
+ "version",
684
+ "createdDate",
685
+ "lastModifiedDate",
686
+ "createdBy",
687
+ "lastModifiedBy",
688
+ "__v"
689
+ ];
690
+ changeStream.on("change", async (data) => {
691
+ const globalId = `${data.ns.coll}/${data.documentKey._id.toHexString()}`;
692
+ const previousSnapshot = await AuditModel.findOne({
693
+ globalId
694
+ }).sort({ version: -1 }).exec();
695
+ const stateBeforeChange = previousSnapshot?.state;
696
+ if (["delete", "update", "replace"].includes(data.operationType)) {
697
+ const doc = await mongoose.connection.db?.collection(data.ns.coll).findOne({ _id: { $eq: data.documentKey._id } });
698
+ const state = doc ? { ...doc, id: doc._id.toHexString() } : undefined;
699
+ const changes = data.operationType === "delete" ? [] : diff(omit2(stateBeforeChange, options.omitFields ?? []), omit2(state, options.omitFields ?? []));
700
+ await AuditModel.create({
701
+ type: data.operationType === "delete" ? "TERMINAL" : "UPDATE",
702
+ author: options.author,
703
+ changes,
704
+ properties: changes.flatMap((change) => change.path.toString()),
705
+ state: state ?? previousSnapshot?.state ?? {
706
+ _id: data.documentKey._id,
707
+ id: data.documentKey._id.toHexString()
708
+ },
709
+ version: (previousSnapshot?.version ?? 0) + 1,
710
+ globalId
711
+ });
712
+ } else {
713
+ const state = data.fullDocument ? {
714
+ ...data.fullDocument,
715
+ id: data.fullDocument._id.toHexString()
716
+ } : undefined;
717
+ const changes = diff({}, omit2(state, options.omitFields ?? []));
718
+ await AuditModel.create({
719
+ type: "INITIAL",
720
+ author: options.author,
721
+ changes,
722
+ version: 0,
723
+ properties: changes.flatMap((change) => change.path.toString()),
724
+ state,
725
+ globalId
726
+ });
727
+ }
728
+ });
729
+ }
730
+ function runWithChangesListening(bloc, options) {
731
+ return (options.mutex ?? mutex).runExclusive(async () => {
732
+ const changeStreams = options.models.map((model) => model.watch(MONGO_UTILS.auditPipeline));
733
+ changeStreams.forEach((changeStream) => {
734
+ auditChanges(changeStream, omit2(options, "models"));
735
+ });
736
+ try {
737
+ return await mongoose.connection.transaction(async () => {
738
+ return await bloc();
739
+ }, { readPreference: "primary" });
740
+ } finally {
741
+ await delay(1);
742
+ for (const changeStream of changeStreams) {
743
+ await changeStream.close();
744
+ }
745
+ }
746
+ });
747
+ }
748
+ var AUDIT_EVENT = "event:audit";
749
+ function registerAuditSubscription({ pubsub }) {
750
+ const AUDIT_CHANGE_STREAM = AuditModel.watch([
751
+ {
752
+ $match: {
753
+ operationType: "insert"
754
+ }
755
+ }
756
+ ]);
757
+ AUDIT_CHANGE_STREAM.on("change", async (data) => {
758
+ try {
759
+ pubsub.publish(AUDIT_EVENT, await AuditModel.findById(data.documentKey._id));
760
+ } catch (error2) {
761
+ logger2.error(error2);
762
+ }
763
+ });
764
+ }
765
+
766
+ // src/audit/audit.model.ts
767
+ var schema2 = new Schema({
768
+ type: {
769
+ type: String,
770
+ enum: ["INITIAL" /* INITIAL */, "UPDATE" /* UPDATE */, "TERMINAL" /* TERMINAL */]
771
+ },
772
+ author: String,
773
+ globalId: { type: String, required: true },
774
+ state: { type: Schema.Types.Mixed, required: true },
775
+ properties: [{ type: String, required: true }],
776
+ changes: [{ type: Schema.Types.Mixed, required: true }],
777
+ version: { type: Number, required: true, default: 0 }
778
+ }, {
779
+ timestamps: { createdAt: "date", updatedAt: false },
780
+ versionKey: false
781
+ });
782
+ (() => {
783
+ pagination(schema2);
784
+ })();
785
+ var AuditModel = model("Audit", schema2);
786
+ var AUDIT_CHANGE_STREAM = AuditModel.watch([
787
+ {
788
+ $match: {
789
+ operationType: "insert"
790
+ }
791
+ }
792
+ ]);
793
+ (() => {
794
+ AUDIT_CHANGE_STREAM.on("change", async (data) => {
795
+ try {
796
+ await redis.publish(AUDIT_EVENT, JSON.stringify(await AuditModel.findById(data.documentKey._id)));
797
+ } catch (error2) {
798
+ logger3.error(error2);
799
+ }
800
+ });
801
+ })();
802
+ // src/audit/audit.service.ts
803
+ import { castAsync } from "@nxgt/shared/helpers";
804
+ class AuditService {
805
+ constructor() {
806
+ this.model = AuditModel;
807
+ }
808
+ async findChangesPaginated(options) {
809
+ return castAsync(this.model.paginate(options));
810
+ }
811
+ findChangesPaginatedByCollectionAndOid(oid, collection, options) {
812
+ const collectionFilter = {
813
+ globalId: { $regex: `^${collection}/${oid}$` }
814
+ };
815
+ const filter2 = options.filter ? {
816
+ $and: [collectionFilter, options.filter]
817
+ } : collectionFilter;
818
+ return this.findChangesPaginated({ ...options, filter: filter2 });
819
+ }
820
+ findChangesPaginatedByCollection(collection, options) {
821
+ const collectionFilter = {
822
+ globalId: { $regex: `^${collection}/` }
823
+ };
824
+ const filter2 = options.filter ? {
825
+ $and: [collectionFilter, options.filter]
826
+ } : collectionFilter;
827
+ return this.findChangesPaginated({ ...options, filter: filter2 });
828
+ }
829
+ async findChanges(filter2 = {}) {
830
+ return this.model.find(filter2 ?? {});
831
+ }
832
+ matches(audit, filter2) {
833
+ return audit.globalId.startsWith(`${filter2.collection}/`) && (filter2.type ? filter2.type === audit.type : true);
834
+ }
835
+ extractId(audit) {
836
+ return audit.globalId.split("/")[1];
837
+ }
838
+ }
839
+ // src/integrity/integrity.registry.ts
840
+ import { CustomException as CustomException6 } from "@nxgt/shared-exceptions";
841
+
842
+ class IntegrityRegistry {
843
+ constructor() {
844
+ this.blockers = new Map;
845
+ this.cascades = new Map;
846
+ }
847
+ registerBlocker(options) {
848
+ const { id, model: model2, errorCode, blocker } = options;
849
+ const current = this.blockers.get(model2) ?? [];
850
+ if (current.some((it) => it.id === id))
851
+ return;
852
+ current.push({ id, fn: blocker, errorCode });
853
+ this.blockers.set(model2, current);
854
+ }
855
+ registerCascade(options) {
856
+ const { id, model: model2, cascade } = options;
857
+ const current = this.cascades.get(model2) ?? [];
858
+ if (current.some((it) => it.id === id))
859
+ return;
860
+ current.push({ id, fn: cascade });
861
+ this.cascades.set(model2, current);
862
+ }
863
+ async validateDeletion(model2, ids) {
864
+ const collectionBlockers = this.blockers.get(model2) ?? [];
865
+ const results = await Promise.all(collectionBlockers.map(async ({ fn, errorCode }) => {
866
+ const count = await fn(ids);
867
+ return count > 0 ? errorCode : null;
868
+ }));
869
+ const failures = results.filter(Boolean);
870
+ if (failures.length > 0) {
871
+ throw CustomException6.conflict({
872
+ message: failures[0],
873
+ options: { ids, blockedBy: failures }
874
+ });
875
+ }
876
+ }
877
+ async runCascades(model2, ids) {
878
+ const handlers = this.cascades.get(model2) ?? [];
879
+ await Promise.all(handlers.map(({ fn }) => fn(ids)));
880
+ }
881
+ }
882
+ var integrityRegistry = new IntegrityRegistry;
883
+ // src/migrations/disconnect.ts
884
+ function isTeardownError(err) {
885
+ const name = typeof err === "object" && err !== null && "name" in err ? String(err.name) : "";
886
+ const message = err instanceof Error ? err.message : String(err);
887
+ return name === "MongoClientClosedError" || name === "MongoPoolClosedError" || message.includes("client was closed") || message.includes("closed connection pool");
888
+ }
889
+ function ignoreTeardownError(err) {
890
+ if (!isTeardownError(err)) {
891
+ throw err;
892
+ }
893
+ }
894
+ async function disconnectQuietly() {
895
+ process.on("uncaughtException", ignoreTeardownError);
896
+ process.on("unhandledRejection", ignoreTeardownError);
897
+ try {
898
+ await mongoose.disconnect().catch(ignoreTeardownError);
899
+ await new Promise((resolve) => setImmediate(resolve));
900
+ } finally {
901
+ process.off("uncaughtException", ignoreTeardownError);
902
+ process.off("unhandledRejection", ignoreTeardownError);
903
+ }
904
+ }
905
+
906
+ // src/migrations/migration.runner.ts
907
+ import { Mutex as Mutex2 } from "async-mutex";
908
+
909
+ // src/migrations/migration.model.ts
910
+ import { model as model2, Schema as Schema2 } from "mongoose";
911
+ var migrationSchema = new Schema2({
912
+ name: {
913
+ type: String,
914
+ required: true,
915
+ unique: true,
916
+ index: true
917
+ },
918
+ batch: {
919
+ type: Number,
920
+ required: true
921
+ },
922
+ status: {
923
+ type: String,
924
+ enum: ["up", "failed"],
925
+ required: true
926
+ },
927
+ executedAt: {
928
+ type: Date,
929
+ required: true
930
+ },
931
+ duration: {
932
+ type: Number,
933
+ required: true
934
+ },
935
+ error: {
936
+ type: String
937
+ }
938
+ }, {
939
+ collection: "migrations",
940
+ versionKey: false
941
+ });
942
+ var MigrationModel = model2("Migration", migrationSchema);
943
+
944
+ // src/migrations/migration.utils.ts
945
+ import { readdir, writeFile } from "node:fs/promises";
946
+ import { extname, join, resolve } from "node:path";
947
+ import { createLogger as createLogger2 } from "@nxgt/shared-logging";
948
+ var logger4 = createLogger2({ name: "migrations" });
949
+ var MIGRATION_FILE_PATTERN = /^\d{1,16}-[a-zA-Z0-9_-]+\.ts$/;
950
+ function isMigrationFile(filename) {
951
+ return MIGRATION_FILE_PATTERN.test(filename);
952
+ }
953
+ function migrationName(filename) {
954
+ return filename.slice(0, -extname(filename).length);
955
+ }
956
+ function migrationTimestamp(name) {
957
+ return name.split("-")[0] || "";
958
+ }
959
+ function sortMigrationsAsc(migrations) {
960
+ return [...migrations].sort((a, b) => {
961
+ const tsA = BigInt(migrationTimestamp(a.name));
962
+ const tsB = BigInt(migrationTimestamp(b.name));
963
+ if (tsA < tsB)
964
+ return -1;
965
+ if (tsA > tsB)
966
+ return 1;
967
+ return 0;
968
+ });
969
+ }
970
+ function sortMigrationsDesc(migrations) {
971
+ return [...migrations].sort((a, b) => {
972
+ const tsA = BigInt(migrationTimestamp(a.name));
973
+ const tsB = BigInt(migrationTimestamp(b.name));
974
+ if (tsA < tsB)
975
+ return 1;
976
+ if (tsA > tsB)
977
+ return -1;
978
+ return 0;
979
+ });
980
+ }
981
+ async function createMigrationFile(migrationsDir, description) {
982
+ const timestamp = new Date().toISOString().replace(/[-T:Z]/g, "").slice(0, 14);
983
+ const sanitized = description.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9_-]/g, "");
984
+ const filename = `${timestamp}-${sanitized}.ts`;
985
+ const filePath = join(resolve(migrationsDir), filename);
986
+ const content = `import type { ClientSession, Connection } from 'mongoose';
987
+
988
+ export async function up(db: Connection, session?: ClientSession): Promise<void> {
989
+ // TODO: Implement migration
990
+ }
991
+
992
+ export async function down(db: Connection, session?: ClientSession): Promise<void> {
993
+ // TODO: Implement rollback
994
+ }
995
+ `;
996
+ await writeFile(filePath, content, "utf8");
997
+ try {
998
+ const { spawnSync } = __require("node:child_process");
999
+ spawnSync("bunx", ["@biomejs/biome", "format", "--write", filePath]);
1000
+ } catch {}
1001
+ return filePath;
1002
+ }
1003
+ async function loadMigrationFiles(migrationsDir) {
1004
+ const absoluteDir = resolve(migrationsDir);
1005
+ let filenames;
1006
+ try {
1007
+ filenames = await readdir(absoluteDir);
1008
+ } catch {
1009
+ throw new Error(`migrations.errors.directory-not-found: ${absoluteDir}`);
1010
+ }
1011
+ const migrationFiles = filenames.filter(isMigrationFile);
1012
+ const definitions = await Promise.all(migrationFiles.map(async (filename) => {
1013
+ const filePath = join(absoluteDir, filename);
1014
+ const name = migrationName(filename);
1015
+ const module = await import(filePath);
1016
+ if (typeof module.up !== "function") {
1017
+ throw new Error(`migrations.errors.missing-up-export: ${filename}`);
1018
+ }
1019
+ return {
1020
+ name,
1021
+ filePath,
1022
+ up: module.up,
1023
+ down: typeof module.down === "function" ? module.down : undefined
1024
+ };
1025
+ }));
1026
+ return sortMigrationsAsc(definitions);
1027
+ }
1028
+
1029
+ // src/migrations/migration.runner.ts
1030
+ var mutex2 = new Mutex2;
1031
+
1032
+ class MigrationRunner {
1033
+ constructor(options) {
1034
+ this.migrationsDir = options.migrationsDir;
1035
+ this.connection = options.connection;
1036
+ }
1037
+ async up(options) {
1038
+ await mutex2.runExclusive(async () => {
1039
+ const allMigrations = await loadMigrationFiles(this.migrationsDir);
1040
+ if (options?.name) {
1041
+ await this.runSpecificUp(allMigrations, options.name, options.dryRun);
1042
+ } else {
1043
+ await this.runPendingUp(allMigrations, options?.dryRun);
1044
+ }
1045
+ });
1046
+ }
1047
+ async down(options) {
1048
+ await mutex2.runExclusive(async () => {
1049
+ const allMigrations = await loadMigrationFiles(this.migrationsDir);
1050
+ if (options?.name) {
1051
+ await this.runSpecificDown(allMigrations, options.name, options.dryRun);
1052
+ } else {
1053
+ await this.runLastBatchDown(allMigrations, options?.dryRun);
1054
+ }
1055
+ });
1056
+ }
1057
+ async create(options) {
1058
+ const filePath = await createMigrationFile(this.migrationsDir, options.name);
1059
+ logger4.info(`created migration: ${filePath}`);
1060
+ return filePath;
1061
+ }
1062
+ async list() {
1063
+ const [allMigrations, records] = await Promise.all([
1064
+ loadMigrationFiles(this.migrationsDir),
1065
+ MigrationModel.find({}).sort({ name: 1 }).lean().exec()
1066
+ ]);
1067
+ const recordByName = new Map(records.map((r) => [r.name, r]));
1068
+ return sortMigrationsAsc(allMigrations).map((migration) => {
1069
+ const record = recordByName.get(migration.name);
1070
+ if (!record) {
1071
+ return {
1072
+ name: migration.name,
1073
+ status: "pending"
1074
+ };
1075
+ }
1076
+ return {
1077
+ name: record.name,
1078
+ status: record.status,
1079
+ batch: record.batch,
1080
+ executedAt: record.executedAt
1081
+ };
1082
+ });
1083
+ }
1084
+ async runPendingUp(allMigrations, dryRun) {
1085
+ const executedNames = await this.fetchExecutedNames();
1086
+ const pending = sortMigrationsAsc(allMigrations).filter((m) => !executedNames.has(m.name));
1087
+ if (pending.length === 0) {
1088
+ logger4.info("no pending migrations");
1089
+ return;
1090
+ }
1091
+ if (dryRun) {
1092
+ logger4.info(`[DRY RUN] pending migrations to run: ${pending.length}`);
1093
+ for (const m of pending)
1094
+ logger4.info(`[DRY RUN] → ${m.name}`);
1095
+ return;
1096
+ }
1097
+ const batch = await this.nextBatchNumber();
1098
+ for (const migration of pending) {
1099
+ await this.executeMigrationUp(migration, batch);
1100
+ }
1101
+ }
1102
+ async runSpecificUp(allMigrations, name, dryRun) {
1103
+ const migration = allMigrations.find((m) => m.name === name);
1104
+ if (!migration) {
1105
+ throw new Error(`migrations.errors.not-found: ${name}`);
1106
+ }
1107
+ if (dryRun) {
1108
+ logger4.info(`[DRY RUN] specific migration to run: ${migration.name}`);
1109
+ return;
1110
+ }
1111
+ const batch = await this.nextBatchNumber();
1112
+ await this.executeMigrationUp(migration, batch);
1113
+ }
1114
+ async executeMigrationUp(migration, batch) {
1115
+ logger4.info(`running up → ${migration.name}`);
1116
+ const start = Date.now();
1117
+ const session = await this.connection.startSession();
1118
+ try {
1119
+ await session.withTransaction(async () => {
1120
+ await migration.up(this.connection, session);
1121
+ const duration2 = Date.now() - start;
1122
+ await MigrationModel.findOneAndUpdate({ name: migration.name }, {
1123
+ name: migration.name,
1124
+ batch,
1125
+ status: "up",
1126
+ executedAt: new Date,
1127
+ duration: duration2,
1128
+ error: undefined
1129
+ }, { upsert: true, returnDocument: "after", session }).exec();
1130
+ });
1131
+ const duration = Date.now() - start;
1132
+ logger4.info(`✓ ${migration.name} (${duration}ms)`);
1133
+ } catch (err) {
1134
+ const duration = Date.now() - start;
1135
+ const errorMessage = err instanceof Error ? err.message : String(err);
1136
+ await MigrationModel.findOneAndUpdate({ name: migration.name }, {
1137
+ name: migration.name,
1138
+ batch,
1139
+ status: "failed",
1140
+ executedAt: new Date,
1141
+ duration,
1142
+ error: errorMessage
1143
+ }, { upsert: true, returnDocument: "after" }).exec();
1144
+ logger4.error(`✗ ${migration.name} — ${errorMessage}`);
1145
+ throw err;
1146
+ } finally {
1147
+ await session.endSession();
1148
+ }
1149
+ }
1150
+ async runLastBatchDown(allMigrations, dryRun) {
1151
+ const lastBatch = await this.fetchLastBatch();
1152
+ if (lastBatch === null) {
1153
+ logger4.info("nothing to roll back");
1154
+ return;
1155
+ }
1156
+ const lastBatchRecords = await MigrationModel.find({ batch: lastBatch }).sort({ name: -1 }).lean().exec();
1157
+ const migrationByName = new Map(allMigrations.map((m) => [m.name, m]));
1158
+ const toRollback = lastBatchRecords.map((r) => migrationByName.get(r.name)).filter((m) => m !== undefined);
1159
+ if (dryRun) {
1160
+ logger4.info(`[DRY RUN] migrations to roll back (batch ${lastBatch}):`);
1161
+ for (const m of sortMigrationsDesc(toRollback))
1162
+ logger4.info(`[DRY RUN] → ${m.name}`);
1163
+ return;
1164
+ }
1165
+ for (const record of sortMigrationsDesc(toRollback)) {
1166
+ await this.executeMigrationDown(record);
1167
+ }
1168
+ }
1169
+ async runSpecificDown(allMigrations, name, dryRun) {
1170
+ const migration = allMigrations.find((m) => m.name === name);
1171
+ if (!migration) {
1172
+ throw new Error(`migrations.errors.not-found: ${name}`);
1173
+ }
1174
+ if (dryRun) {
1175
+ logger4.info(`[DRY RUN] specific migration to roll back: ${migration.name}`);
1176
+ return;
1177
+ }
1178
+ await this.executeMigrationDown(migration);
1179
+ }
1180
+ async executeMigrationDown(migration) {
1181
+ if (!migration.down) {
1182
+ throw new Error(`migrations.errors.missing-down-export: ${migration.name}`);
1183
+ }
1184
+ logger4.info(`running down → ${migration.name}`);
1185
+ const start = Date.now();
1186
+ const session = await this.connection.startSession();
1187
+ try {
1188
+ await session.withTransaction(async () => {
1189
+ await migration.down?.(this.connection, session);
1190
+ await MigrationModel.deleteOne({ name: migration.name }).session(session).exec();
1191
+ });
1192
+ const duration = Date.now() - start;
1193
+ logger4.info(`✓ rolled back ${migration.name} (${duration}ms)`);
1194
+ } catch (err) {
1195
+ const errorMessage = err instanceof Error ? err.message : String(err);
1196
+ logger4.error(`✗ rollback failed ${migration.name} — ${errorMessage}`);
1197
+ throw err;
1198
+ } finally {
1199
+ await session.endSession();
1200
+ }
1201
+ }
1202
+ async fetchExecutedNames() {
1203
+ const records = await MigrationModel.find({ status: "up" }, { name: 1 }).lean().exec();
1204
+ return new Set(records.map((r) => r.name));
1205
+ }
1206
+ async fetchLastBatch() {
1207
+ const record = await MigrationModel.findOne({ status: "up" }).sort({ batch: -1 }).select("batch").lean().exec();
1208
+ return record?.batch ?? null;
1209
+ }
1210
+ async nextBatchNumber() {
1211
+ const last = await this.fetchLastBatch();
1212
+ return (last ?? 0) + 1;
1213
+ }
1214
+ }
1215
+
1216
+ // src/migrations/migrate.ts
1217
+ function resolveUri(options) {
1218
+ const uri = options.uri ?? Bun.env.MONGODB_URI;
1219
+ if (!uri) {
1220
+ throw new Error("migrations.errors.missing-uri: provide `uri` in options or set MONGODB_URI");
1221
+ }
1222
+ return uri;
1223
+ }
1224
+ async function migrate(operation = "list", options) {
1225
+ if (operation === "create") {
1226
+ const runner2 = new MigrationRunner({
1227
+ migrationsDir: options.migrationsDir,
1228
+ connection: null
1229
+ });
1230
+ await runner2.create(options.create ?? { name: "unnamed-migration" });
1231
+ return;
1232
+ }
1233
+ const uri = resolveUri(options);
1234
+ await mongoose.connect(uri);
1235
+ const runner = new MigrationRunner({
1236
+ migrationsDir: options.migrationsDir,
1237
+ connection: mongoose.connection
1238
+ });
1239
+ try {
1240
+ if (operation === "list") {
1241
+ await runner.list();
1242
+ } else {
1243
+ await runner[operation](options[operation]);
1244
+ }
1245
+ } finally {
1246
+ await disconnectQuietly();
1247
+ }
1248
+ }
1249
+ async function withMigrationRunner(options, fn) {
1250
+ const uri = resolveUri(options);
1251
+ await mongoose.connect(uri);
1252
+ const runner = new MigrationRunner({
1253
+ migrationsDir: options.migrationsDir,
1254
+ connection: mongoose.connection
1255
+ });
1256
+ try {
1257
+ return await fn(runner);
1258
+ } finally {
1259
+ await disconnectQuietly();
1260
+ }
1261
+ }
1262
+ // src/models/postal-address.model.ts
1263
+ import { Schema as Schema3 } from "mongoose";
1264
+
1265
+ // src/validation/validators/index.ts
1266
+ var exports_validators = {};
1267
+ __export(exports_validators, {
1268
+ email: () => email,
1269
+ future: () => future,
1270
+ guid: () => guid,
1271
+ ip: () => ip,
1272
+ ipv4: () => ipv4,
1273
+ ipv6: () => ipv6,
1274
+ mac: () => mac,
1275
+ max: () => max,
1276
+ maxLength: () => maxLength,
1277
+ min: () => min,
1278
+ minLength: () => minLength,
1279
+ negative: () => negative,
1280
+ notBlank: () => notBlank,
1281
+ notEmpty: () => notEmpty,
1282
+ objectId: () => objectId,
1283
+ past: () => past,
1284
+ pattern: () => pattern,
1285
+ phone: () => phone,
1286
+ positive: () => positive,
1287
+ range: () => range,
1288
+ required: () => required,
1289
+ size: () => size,
1290
+ ssn: () => ssn,
1291
+ street: () => street,
1292
+ time: () => time,
1293
+ url: () => url,
1294
+ uuid: () => uuid,
1295
+ zipCode: () => zipCode
1296
+ });
1297
+
1298
+ // src/validation/validators/email.validator.ts
1299
+ import { REGEX } from "@nxgt/shared/helpers";
1300
+ import { isNil as isNil3 } from "lodash";
1301
+
1302
+ // src/validation/validation.utils.ts
1303
+ import { translate as translate2 } from "@nxgt/i18n";
1304
+ import { cast } from "@nxgt/shared/helpers";
1305
+ import { isArray, isNil as isNil2 } from "lodash";
1306
+ function parseValidatorOptions(args) {
1307
+ function parseField(field2) {
1308
+ return isArray(field2) && field2.length === 2 && typeof field2[1] === "string" ? { value: field2[0], message: field2[1] } : { value: cast(field2) };
1309
+ }
1310
+ const field = parseField(args);
1311
+ return {
1312
+ ...field,
1313
+ message: (options, defaultMessage) => (props) => {
1314
+ return translate2(isNil2(props.value) ? "validation.errors.required" : field.message ?? defaultMessage ?? "", {
1315
+ ...props,
1316
+ ...options
1317
+ });
1318
+ }
1319
+ };
1320
+ }
1321
+
1322
+ // src/validation/validators/email.validator.ts
1323
+ function email(args) {
1324
+ const options = parseValidatorOptions(args);
1325
+ return {
1326
+ message: options.message?.({
1327
+ domains: options.value.domains?.join(", "),
1328
+ numberOfDomains: options.value.domains?.length ?? 0
1329
+ }, "validation.errors.email"),
1330
+ validator(value) {
1331
+ return isNil3(value) || typeof value === "string" && REGEX.email.test(value) && (options.value.domains?.length ? options.value.domains.filter((domain) => value.includes(domain)).length === options.value.domains.length : true);
1332
+ }
1333
+ };
1334
+ }
1335
+
1336
+ // src/validation/validators/future.validator.ts
1337
+ import i18next from "i18next";
1338
+ import { isNil as isNil4, omit as omit3 } from "lodash";
1339
+ import { DateTime } from "luxon";
1340
+ function future(args) {
1341
+ const options = parseValidatorOptions(args);
1342
+ const limit = DateTime.now().minus(omit3(options.value, "datetime"));
1343
+ return {
1344
+ message: options.message?.({
1345
+ limit: limit.setLocale(i18next.language).toLocaleString(options.value.datetime ?? true ? DateTime.DATETIME_FULL : DateTime.DATE_FULL)
1346
+ }, "validation.errors.future"),
1347
+ validator(value) {
1348
+ return isNil4(value) || value instanceof Date && DateTime.fromJSDate(value).isValid && DateTime.fromJSDate(value) >= limit;
1349
+ }
1350
+ };
1351
+ }
1352
+
1353
+ // src/validation/validators/guid.validator.ts
1354
+ import { REGEX as REGEX2 } from "@nxgt/shared/helpers";
1355
+ import { isNil as isNil5 } from "lodash";
1356
+ function guid(args) {
1357
+ const options = parseValidatorOptions(args);
1358
+ return {
1359
+ message: options.message?.({}, "validation.errors.guid"),
1360
+ validator(value) {
1361
+ return isNil5(value) || typeof value === "string" && REGEX2.guid.test(value);
1362
+ }
1363
+ };
1364
+ }
1365
+
1366
+ // src/validation/validators/ip.validator.ts
1367
+ import { REGEX as REGEX3 } from "@nxgt/shared/helpers";
1368
+ import { isNil as isNil6 } from "lodash";
1369
+ function ip(args) {
1370
+ const options = parseValidatorOptions(args);
1371
+ return {
1372
+ message: options.message?.({}, "validation.errors.ip"),
1373
+ validator(value) {
1374
+ return isNil6(value) || typeof value === "string" && REGEX3.ip.test(value);
1375
+ }
1376
+ };
1377
+ }
1378
+
1379
+ // src/validation/validators/ipv4.validator.ts
1380
+ import { REGEX as REGEX4 } from "@nxgt/shared/helpers";
1381
+ import { isNil as isNil7 } from "lodash";
1382
+ function ipv4(args) {
1383
+ const options = parseValidatorOptions(args);
1384
+ return {
1385
+ message: options.message?.({}, "validation.errors.ipv4"),
1386
+ validator(value) {
1387
+ return isNil7(value) || typeof value === "string" && REGEX4.ipv4.test(value);
1388
+ }
1389
+ };
1390
+ }
1391
+
1392
+ // src/validation/validators/ipv6.validator.ts
1393
+ import { REGEX as REGEX5 } from "@nxgt/shared/helpers";
1394
+ import { isNil as isNil8 } from "lodash";
1395
+ function ipv6(args) {
1396
+ const options = parseValidatorOptions(args);
1397
+ return {
1398
+ message: options.message?.({}, "validation.errors.ipv6"),
1399
+ validator(value) {
1400
+ return isNil8(value) || typeof value === "string" && REGEX5.ipv6.test(value);
1401
+ }
1402
+ };
1403
+ }
1404
+
1405
+ // src/validation/validators/mac.validator.ts
1406
+ import { REGEX as REGEX6 } from "@nxgt/shared/helpers";
1407
+ import { isNil as isNil9 } from "lodash";
1408
+ function mac(args) {
1409
+ const options = parseValidatorOptions(args);
1410
+ return {
1411
+ message: options.message?.({}, "validation.errors.mac"),
1412
+ validator(value) {
1413
+ return isNil9(value) || typeof value === "string" && REGEX6.mac.test(value);
1414
+ }
1415
+ };
1416
+ }
1417
+
1418
+ // src/validation/validators/max.validator.ts
1419
+ import { isNil as isNil10 } from "lodash";
1420
+ function max(args) {
1421
+ const options = parseValidatorOptions(args);
1422
+ return {
1423
+ message: options.message?.({ max: options.value }, "validation.errors.max"),
1424
+ validator(value) {
1425
+ return isNil10(value) || typeof value === "number" && value <= options.value;
1426
+ }
1427
+ };
1428
+ }
1429
+
1430
+ // src/validation/validators/max-length.validator.ts
1431
+ import { isArray as isArray2, isNil as isNil11 } from "lodash";
1432
+ function maxLength(args) {
1433
+ const options = parseValidatorOptions(args);
1434
+ return {
1435
+ message: options.message?.({ maxLength: options.value }, "validation.errors.max-length"),
1436
+ validator(value) {
1437
+ return isNil11(value) || typeof value === "string" && value.length <= options.value || isArray2(value) && value.length <= options.value;
1438
+ }
1439
+ };
1440
+ }
1441
+
1442
+ // src/validation/validators/min.validator.ts
1443
+ import { isNil as isNil12 } from "lodash";
1444
+ function min(args) {
1445
+ const options = parseValidatorOptions(args);
1446
+ return {
1447
+ message: options.message?.({ min: options.value }, "validation.errors.min"),
1448
+ validator(value) {
1449
+ return isNil12(value) || typeof value === "number" && value >= options.value;
1450
+ }
1451
+ };
1452
+ }
1453
+
1454
+ // src/validation/validators/min-length.validator.ts
1455
+ import { isArray as isArray3, isNil as isNil13 } from "lodash";
1456
+ function minLength(args) {
1457
+ const options = parseValidatorOptions(args);
1458
+ return {
1459
+ message: options.message?.({ minLength: options.value }, "validation.errors.min-length"),
1460
+ validator(value) {
1461
+ return isNil13(value) || typeof value === "string" && value.length >= options.value || isArray3(value) && value.length >= options.value;
1462
+ }
1463
+ };
1464
+ }
1465
+
1466
+ // src/validation/validators/negative.validator.ts
1467
+ import { isNil as isNil14 } from "lodash";
1468
+ function negative(args) {
1469
+ const options = parseValidatorOptions(args);
1470
+ return {
1471
+ message: options.message?.({}, "validation.errors.negative"),
1472
+ validator(value) {
1473
+ return isNil14(value) || typeof value === "number" && value <= 0;
1474
+ }
1475
+ };
1476
+ }
1477
+
1478
+ // src/validation/validators/not-blank.validator.ts
1479
+ import { isEmpty, isNil as isNil15 } from "lodash";
1480
+ function notBlank(args) {
1481
+ const options = parseValidatorOptions(args);
1482
+ return {
1483
+ message: options.message?.({}, "validation.errors.not-blank"),
1484
+ validator(value) {
1485
+ return isNil15(value) || typeof value === "string" && !isEmpty(value.trim());
1486
+ }
1487
+ };
1488
+ }
1489
+
1490
+ // src/validation/validators/not-empty.validator.ts
1491
+ import { isArray as isArray4, isEmpty as isEmpty2, isNil as isNil16 } from "lodash";
1492
+ function notEmpty(args) {
1493
+ const options = parseValidatorOptions(args);
1494
+ return {
1495
+ message: options.message?.({}, "validation.errors.not-empty"),
1496
+ validator(value) {
1497
+ return isNil16(value) || (typeof value === "string" || isArray4(value)) && !isEmpty2(value);
1498
+ }
1499
+ };
1500
+ }
1501
+
1502
+ // src/validation/validators/object-id.validator.ts
1503
+ import { REGEX as REGEX7 } from "@nxgt/shared/helpers";
1504
+ import { isNil as isNil17 } from "lodash";
1505
+ function objectId(args) {
1506
+ const options = parseValidatorOptions(args);
1507
+ return {
1508
+ message: options.message?.({}, "validation.errors.object-id"),
1509
+ validator(value) {
1510
+ return isNil17(value) || typeof value === "string" && REGEX7.objectId.test(value);
1511
+ }
1512
+ };
1513
+ }
1514
+
1515
+ // src/validation/validators/past.validator.ts
1516
+ import i18next2 from "i18next";
1517
+ import { isNil as isNil18, omit as omit4 } from "lodash";
1518
+ import { DateTime as DateTime2 } from "luxon";
1519
+ function past(args) {
1520
+ const options = parseValidatorOptions(args);
1521
+ const limit = DateTime2.now().minus(omit4(options.value, "datetime"));
1522
+ return {
1523
+ message: options.message?.({
1524
+ limit: limit.setLocale(i18next2.language).toLocaleString(options.value.datetime ?? true ? DateTime2.DATETIME_FULL : DateTime2.DATE_FULL)
1525
+ }, "validation.errors.past"),
1526
+ validator(value) {
1527
+ return isNil18(value) || value instanceof Date && DateTime2.fromJSDate(value).isValid && DateTime2.fromJSDate(value) <= limit;
1528
+ }
1529
+ };
1530
+ }
1531
+
1532
+ // src/validation/validators/pattern.validator.ts
1533
+ import { REGEX as REGEX8 } from "@nxgt/shared/helpers";
1534
+ import { isNil as isNil19 } from "lodash";
1535
+ function pattern(args) {
1536
+ const options = parseValidatorOptions(args);
1537
+ return {
1538
+ message: options.message?.({ pattern: options.value }, "validation.errors.pattern"),
1539
+ validator(value) {
1540
+ return isNil19(value) || REGEX8.regex(options.value).test(value.toString());
1541
+ }
1542
+ };
1543
+ }
1544
+
1545
+ // src/validation/validators/phone.validator.ts
1546
+ import { REGEX as REGEX9 } from "@nxgt/shared/helpers";
1547
+ import { isNil as isNil20 } from "lodash";
1548
+ function phone(args) {
1549
+ const options = parseValidatorOptions(args);
1550
+ return {
1551
+ message: options.message?.({}, "validation.errors.phone"),
1552
+ validator(value) {
1553
+ return isNil20(value) || typeof value === "string" && REGEX9.phone.test(value);
1554
+ }
1555
+ };
1556
+ }
1557
+
1558
+ // src/validation/validators/positive.validator.ts
1559
+ import { isNil as isNil21 } from "lodash";
1560
+ function positive(args) {
1561
+ const options = parseValidatorOptions(args);
1562
+ return {
1563
+ message: options.message?.({}, "validation.errors.positive"),
1564
+ validator(value) {
1565
+ return isNil21(value) || typeof value === "number" && value >= 0;
1566
+ }
1567
+ };
1568
+ }
1569
+
1570
+ // src/validation/validators/range.validator.ts
1571
+ import { isNil as isNil22 } from "lodash";
1572
+ function range(args) {
1573
+ const options = parseValidatorOptions(args);
1574
+ if (options.value[0] > options.value[1]) {
1575
+ throw new Error("First argument must ne less than or equal to the second argument");
1576
+ }
1577
+ return {
1578
+ message: options.message?.({ min: options.value[0], max: options.value[1] }, "validation.errors.range"),
1579
+ validator(value) {
1580
+ return isNil22(value) || typeof value === "number" && value >= options.value[0] && value <= options.value[1];
1581
+ }
1582
+ };
1583
+ }
1584
+
1585
+ // src/validation/validators/required.validator.ts
1586
+ import { isEmpty as isEmpty3, isNil as isNil23 } from "lodash";
1587
+ function required(args) {
1588
+ const options = parseValidatorOptions(args);
1589
+ return {
1590
+ message: options.message?.({}, "validation.errors.required"),
1591
+ validator(value) {
1592
+ return typeof value === "number" ? !isNil23(value) : !isNil23(value) && !isEmpty3(value);
1593
+ }
1594
+ };
1595
+ }
1596
+
1597
+ // src/validation/validators/size.validator.ts
1598
+ import { isArray as isArray5, isNil as isNil24 } from "lodash";
1599
+ function size(args) {
1600
+ const options = parseValidatorOptions(args);
1601
+ if (options.value[0] > options.value[1]) {
1602
+ throw new Error("First argument must ne less than or equal to the second argument");
1603
+ }
1604
+ return {
1605
+ message: options.message?.({ min: options.value[0], max: options.value[1] }, "validation.errors.size"),
1606
+ validator(value) {
1607
+ return isNil24(value) || (typeof value === "string" || isArray5(value)) && value.length >= options.value[0] && value.length <= options.value[1];
1608
+ }
1609
+ };
1610
+ }
1611
+
1612
+ // src/validation/validators/ssn.validator.ts
1613
+ import { REGEX as REGEX10 } from "@nxgt/shared/helpers";
1614
+ import { isNil as isNil25 } from "lodash";
1615
+ function ssn(args) {
1616
+ const options = parseValidatorOptions(args);
1617
+ return {
1618
+ message: options.message?.({}, "validation.errors.ssn"),
1619
+ validator(value) {
1620
+ return isNil25(value) || typeof value === "string" && REGEX10.ssn.test(value);
1621
+ }
1622
+ };
1623
+ }
1624
+
1625
+ // src/validation/validators/street.validator.ts
1626
+ import { REGEX as REGEX11 } from "@nxgt/shared/helpers";
1627
+ import { isNil as isNil26 } from "lodash";
1628
+ function street(args) {
1629
+ const options = parseValidatorOptions(args);
1630
+ return {
1631
+ message: options.message?.({}, "validation.errors.street"),
1632
+ validator(value) {
1633
+ return isNil26(value) || typeof value === "string" && REGEX11.street.test(value);
1634
+ }
1635
+ };
1636
+ }
1637
+
1638
+ // src/validation/validators/time.validator.ts
1639
+ import { REGEX as REGEX12 } from "@nxgt/shared/helpers";
1640
+ import { isNil as isNil27 } from "lodash";
1641
+ function time(args) {
1642
+ const options = parseValidatorOptions(args);
1643
+ return {
1644
+ message: options.message?.({}, "validation.errors.time"),
1645
+ validator(value) {
1646
+ return isNil27(value) || typeof value === "string" && REGEX12.time.test(value);
1647
+ }
1648
+ };
1649
+ }
1650
+
1651
+ // src/validation/validators/url.validator.ts
1652
+ import { REGEX as REGEX13 } from "@nxgt/shared/helpers";
1653
+ import { isNil as isNil28 } from "lodash";
1654
+ function url(args) {
1655
+ const options = parseValidatorOptions(args);
1656
+ return {
1657
+ message: options.message?.({
1658
+ domains: options.value.domains?.join(", "),
1659
+ numberOfDomains: options.value.domains?.length ?? 0
1660
+ }, "validation.errors.url"),
1661
+ validator(value) {
1662
+ return isNil28(value) || typeof value === "string" && REGEX13.url.test(value) && (options.value.domains?.length ? options.value.domains.filter((domain) => value.includes(domain)).length === options.value.domains.length : true);
1663
+ }
1664
+ };
1665
+ }
1666
+
1667
+ // src/validation/validators/uuid.validator.ts
1668
+ import { REGEX as REGEX14 } from "@nxgt/shared/helpers";
1669
+ import { isNil as isNil29 } from "lodash";
1670
+ function uuid(args) {
1671
+ const options = parseValidatorOptions(args);
1672
+ return {
1673
+ message: options.message?.({}, "validation.errors.uuid"),
1674
+ validator(value) {
1675
+ return isNil29(value) || typeof value === "string" && REGEX14.uuid.test(value);
1676
+ }
1677
+ };
1678
+ }
1679
+
1680
+ // src/validation/validators/zip-code.validator.ts
1681
+ import { REGEX as REGEX15 } from "@nxgt/shared/helpers";
1682
+ import { isNil as isNil30 } from "lodash";
1683
+ function zipCode(args) {
1684
+ const options = parseValidatorOptions(args);
1685
+ return {
1686
+ message: options.message?.({}, "validation.errors.zip-code"),
1687
+ validator(value) {
1688
+ return isNil30(value) || typeof value === "string" && REGEX15.zipCode.test(value);
1689
+ }
1690
+ };
1691
+ }
1692
+ // src/models/postal-address.model.ts
1693
+ var PostalAddressSchema = new Schema3({
1694
+ street: { type: String, validate: exports_validators.maxLength(200) },
1695
+ locality: { type: String, validate: exports_validators.maxLength(100) },
1696
+ region: { type: String, validate: exports_validators.maxLength(100) },
1697
+ postalCode: { type: String, validate: exports_validators.maxLength(20) },
1698
+ country: { type: String, validate: exports_validators.size([2, 2]) }
1699
+ }, { _id: false });
1700
+ // src/services/crud.service.ts
1701
+ import { cleanObject } from "@nxgt/shared/helpers";
1702
+ import { CustomException as CustomException7 } from "@nxgt/shared-exceptions";
1703
+ class MongoCrudService {
1704
+ constructor(principal) {
1705
+ this.principal = principal;
1706
+ this.extraModels = [];
1707
+ }
1708
+ get changesOptions() {
1709
+ return {
1710
+ models: [this.model, ...this.extraModels],
1711
+ author: this.principal?.name || "system",
1712
+ mutex: this.mutex
1713
+ };
1714
+ }
1715
+ async find(filter2 = {}) {
1716
+ return this.model.find(filter2).exec();
1717
+ }
1718
+ async findById(id) {
1719
+ const entity = await this.model.findById(id).exec();
1720
+ if (!entity) {
1721
+ throw CustomException7.notFound({
1722
+ message: `${this.model.collection.name}.errors.not-found`
1723
+ });
1724
+ }
1725
+ return entity;
1726
+ }
1727
+ async create(input) {
1728
+ const entity = await runWithChangesListening(async () => {
1729
+ await this.beforeCreate(input);
1730
+ const data = await this.buildCreateData(input);
1731
+ return this.model.create({
1732
+ ...data,
1733
+ createdBy: this.principal?.name,
1734
+ lastModifiedBy: this.principal?.name
1735
+ });
1736
+ }, this.changesOptions);
1737
+ return this.findById(entity.id);
1738
+ }
1739
+ async findWritableById(id) {
1740
+ const entity = await this.model.findById(id).exec();
1741
+ if (!entity) {
1742
+ throw CustomException7.notFound({
1743
+ message: `${this.model.collection.name}.errors.not-found`
1744
+ });
1745
+ }
1746
+ return entity;
1747
+ }
1748
+ async update(id, input) {
1749
+ return runWithChangesListening(async () => {
1750
+ const doc = await this.findWritableById(id);
1751
+ await this.beforeUpdate(doc, input);
1752
+ const data = await this.buildUpdateData(doc, input);
1753
+ Object.assign(doc, cleanObject({
1754
+ ...data,
1755
+ lastModifiedBy: this.principal?.name
1756
+ }));
1757
+ await doc.save();
1758
+ return this.findById(id);
1759
+ }, this.changesOptions);
1760
+ }
1761
+ async deleteMany(ids) {
1762
+ await runWithChangesListening(async () => {
1763
+ await this.beforeDeleteMany(ids);
1764
+ await integrityRegistry.validateDeletion(this.model.modelName, ids);
1765
+ await integrityRegistry.runCascades(this.model.modelName, ids);
1766
+ return this.model.deleteMany({ _id: { $in: ids } }).exec();
1767
+ }, this.changesOptions);
1768
+ }
1769
+ async delete(id) {
1770
+ await this.model.ensureExists({ _id: id }, {
1771
+ message: `${this.model.collection.name}.errors.not-found`
1772
+ });
1773
+ await this.deleteMany([id]);
1774
+ }
1775
+ buildCreateData(input) {
1776
+ return input;
1777
+ }
1778
+ buildUpdateData(_doc, input) {
1779
+ return input;
1780
+ }
1781
+ async beforeDeleteMany(_ids) {}
1782
+ async beforeCreate(_input) {}
1783
+ async beforeUpdate(_doc, _input) {}
1784
+ }
1785
+ export {
1786
+ AUDIT_CHANGE_STREAM,
1787
+ AUDIT_EVENT,
1788
+ AuditModel,
1789
+ AuditService,
1790
+ ChangeOperationType,
1791
+ CommitType,
1792
+ CursorPaginationOptions,
1793
+ IntegrityRegistry,
1794
+ MONGOOSE_PLUGINS,
1795
+ MONGO_UTILS,
1796
+ MigrationModel,
1797
+ MigrationRunner,
1798
+ MongoCrudService,
1799
+ NestedOffsetPaginationOptions,
1800
+ OffsetPaginationOptions,
1801
+ PLUGIN_OPTIONS,
1802
+ PostalAddressSchema,
1803
+ SortDirection,
1804
+ exports_validators as VALIDATORS,
1805
+ addFilterCondition,
1806
+ applyPlugins,
1807
+ auditChanges,
1808
+ buildArrayFilter,
1809
+ buildDateRangeFilter,
1810
+ buildListStringPatch,
1811
+ buildListStringPatchUpdate,
1812
+ buildRegexFilter,
1813
+ buildSort,
1814
+ castError,
1815
+ checkVersion,
1816
+ clearDatabase,
1817
+ createMigrationFile,
1818
+ integrityRegistry,
1819
+ isMigrationFile,
1820
+ isValidObjectID,
1821
+ loadMigrationFiles,
1822
+ logger4 as logger,
1823
+ migrate,
1824
+ migrationName,
1825
+ migrationTimestamp,
1826
+ mongoose,
1827
+ objectIdFromString,
1828
+ objectIdString,
1829
+ paginateList,
1830
+ patchListObjectId,
1831
+ patchListString,
1832
+ registerAuditSubscription,
1833
+ resolveListStringPatch,
1834
+ runWithChangesListening,
1835
+ safeCreateView,
1836
+ sortMigrationsAsc,
1837
+ sortMigrationsDesc,
1838
+ toObject,
1839
+ toObjectId,
1840
+ toObjectIds,
1841
+ toObjects,
1842
+ validObjectIds,
1843
+ withMigrationRunner
1844
+ };
1845
+
1846
+ //# debugId=91BF3312614B3B9464756E2164756E21
1847
+ //# sourceMappingURL=index.js.map