@diia-inhouse/db 10.8.12 → 12.3.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 (118) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +27 -0
  2. package/dist/cli/cleanupExpiredData.js +83 -136
  3. package/dist/cli/index.d.ts +1 -0
  4. package/dist/cli/index.js +85 -100
  5. package/dist/cli/runMigration.js +64 -105
  6. package/dist/cli/syncIndexes.js +38 -77
  7. package/dist/index.d.ts +28 -0
  8. package/dist/index.js +23 -22
  9. package/dist/interfaces/database.d.ts +10 -0
  10. package/dist/interfaces/dbConfig.d.ts +37 -0
  11. package/dist/interfaces/dbConfig.js +8 -9
  12. package/dist/interfaces/errors.d.ts +43 -0
  13. package/dist/interfaces/errors.js +42 -50
  14. package/dist/interfaces/index.d.ts +15 -0
  15. package/dist/interfaces/index.js +11 -25
  16. package/dist/interfaces/metrics.d.ts +22 -0
  17. package/dist/interfaces/metrics.js +38 -19
  18. package/dist/interfaces/migrateMongoConfig.d.ts +17 -0
  19. package/dist/interfaces/models/counter.d.ts +8 -0
  20. package/dist/interfaces/models/encryptedStorage.d.ts +9 -0
  21. package/dist/interfaces/mongoStatus.d.ts +16 -0
  22. package/dist/interfaces/mongoStatus.js +11 -12
  23. package/dist/mongo.d.ts +12 -0
  24. package/dist/mongo.js +71 -89
  25. package/dist/postgres.d.ts +4 -0
  26. package/dist/postgres.js +5 -19
  27. package/dist/postgresUtils.d.ts +8 -0
  28. package/dist/postgresUtils.js +14 -18
  29. package/dist/schemas/counter.d.ts +7 -0
  30. package/dist/schemas/counter.js +19 -14
  31. package/dist/schemas/encryptedStorage.d.ts +7 -0
  32. package/dist/schemas/encryptedStorage.js +17 -15
  33. package/dist/schemas/index.d.ts +3 -0
  34. package/dist/schemas/index.js +4 -20
  35. package/dist/schemas/statusHistoryItem.d.ts +7 -0
  36. package/dist/schemas/statusHistoryItem.js +16 -10
  37. package/dist/services/counter.d.ts +19 -0
  38. package/dist/services/counter.js +70 -72
  39. package/dist/services/database.d.ts +50 -0
  40. package/dist/services/database.js +255 -281
  41. package/dist/services/encryptedStorage.d.ts +73 -0
  42. package/dist/services/encryptedStorage.js +178 -214
  43. package/dist/services/index.d.ts +4 -0
  44. package/dist/services/index.js +5 -21
  45. package/dist/services/postgresDatabase.d.ts +12 -0
  46. package/dist/services/postgresDatabase.js +20 -25
  47. package/dist/tables/counter.d.ts +4 -0
  48. package/dist/tables/counter.js +11 -12
  49. package/dist/tables/encryptedStorage.d.ts +4 -0
  50. package/dist/tables/encryptedStorage.js +12 -13
  51. package/dist/tables/index.d.ts +9 -0
  52. package/dist/tables/index.js +3 -19
  53. package/package.json +47 -48
  54. package/dist/cli/cleanupExpiredData.js.map +0 -1
  55. package/dist/cli/index.js.map +0 -1
  56. package/dist/cli/runMigration.js.map +0 -1
  57. package/dist/cli/syncIndexes.js.map +0 -1
  58. package/dist/index.js.map +0 -1
  59. package/dist/interfaces/database.js +0 -3
  60. package/dist/interfaces/database.js.map +0 -1
  61. package/dist/interfaces/dbConfig.js.map +0 -1
  62. package/dist/interfaces/errors.js.map +0 -1
  63. package/dist/interfaces/index.js.map +0 -1
  64. package/dist/interfaces/metrics.js.map +0 -1
  65. package/dist/interfaces/migrateMongoConfig.js +0 -3
  66. package/dist/interfaces/migrateMongoConfig.js.map +0 -1
  67. package/dist/interfaces/models/counter.js +0 -3
  68. package/dist/interfaces/models/counter.js.map +0 -1
  69. package/dist/interfaces/models/encryptedStorage.js +0 -3
  70. package/dist/interfaces/models/encryptedStorage.js.map +0 -1
  71. package/dist/interfaces/models/index.js +0 -19
  72. package/dist/interfaces/models/index.js.map +0 -1
  73. package/dist/interfaces/mongoStatus.js.map +0 -1
  74. package/dist/mongo.js.map +0 -1
  75. package/dist/postgres.js.map +0 -1
  76. package/dist/postgresUtils.js.map +0 -1
  77. package/dist/schemas/counter.js.map +0 -1
  78. package/dist/schemas/encryptedStorage.js.map +0 -1
  79. package/dist/schemas/index.js.map +0 -1
  80. package/dist/schemas/statusHistoryItem.js.map +0 -1
  81. package/dist/services/counter.js.map +0 -1
  82. package/dist/services/database.js.map +0 -1
  83. package/dist/services/encryptedStorage.js.map +0 -1
  84. package/dist/services/index.js.map +0 -1
  85. package/dist/services/postgresDatabase.js.map +0 -1
  86. package/dist/tables/counter.js.map +0 -1
  87. package/dist/tables/encryptedStorage.js.map +0 -1
  88. package/dist/tables/index.js.map +0 -1
  89. package/dist/types/cli/cleanupExpiredData.d.ts +0 -11
  90. package/dist/types/cli/index.d.ts +0 -2
  91. package/dist/types/cli/runMigration.d.ts +0 -11
  92. package/dist/types/cli/syncIndexes.d.ts +0 -7
  93. package/dist/types/index.d.ts +0 -5
  94. package/dist/types/interfaces/database.d.ts +0 -8
  95. package/dist/types/interfaces/dbConfig.d.ts +0 -33
  96. package/dist/types/interfaces/errors.d.ts +0 -40
  97. package/dist/types/interfaces/index.d.ts +0 -8
  98. package/dist/types/interfaces/metrics.d.ts +0 -20
  99. package/dist/types/interfaces/migrateMongoConfig.d.ts +0 -13
  100. package/dist/types/interfaces/models/counter.d.ts +0 -5
  101. package/dist/types/interfaces/models/encryptedStorage.d.ts +0 -6
  102. package/dist/types/interfaces/models/index.d.ts +0 -2
  103. package/dist/types/interfaces/mongoStatus.d.ts +0 -12
  104. package/dist/types/mongo.d.ts +0 -9
  105. package/dist/types/postgres.d.ts +0 -2
  106. package/dist/types/postgresUtils.d.ts +0 -26
  107. package/dist/types/schemas/counter.d.ts +0 -12
  108. package/dist/types/schemas/encryptedStorage.d.ts +0 -12
  109. package/dist/types/schemas/index.d.ts +0 -3
  110. package/dist/types/schemas/statusHistoryItem.d.ts +0 -3
  111. package/dist/types/services/counter.d.ts +0 -15
  112. package/dist/types/services/database.d.ts +0 -43
  113. package/dist/types/services/encryptedStorage.d.ts +0 -69
  114. package/dist/types/services/index.d.ts +0 -4
  115. package/dist/types/services/postgresDatabase.d.ts +0 -8
  116. package/dist/types/tables/counter.d.ts +0 -94
  117. package/dist/types/tables/encryptedStorage.d.ts +0 -111
  118. package/dist/types/tables/index.d.ts +0 -2
@@ -1,282 +1,256 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
1
+ import "../interfaces/dbConfig.js";
2
+ import "../interfaces/mongoStatus.js";
3
+ import { mongodbOperationsAllowedFields, mongodbOperationsDefaultBuckets } from "../interfaces/metrics.js";
4
+ import "../interfaces/index.js";
5
+ import mongoose from "mongoose";
6
+ import lodash from "lodash";
7
+ import { Histogram, Observer } from "@diia-inhouse/diia-metrics";
8
+ import { DatabaseError } from "@diia-inhouse/errors";
9
+ import { HttpStatusCode } from "@diia-inhouse/types";
10
+ //#region src/services/database.ts
11
+ const { cloneDeep } = lodash;
12
+ var DatabaseService = class {
13
+ databaseAdapter;
14
+ dbConfigs;
15
+ envService;
16
+ logger;
17
+ defaultModelsDir = "models";
18
+ db = {};
19
+ dbOperationsHistogram;
20
+ dbOplogObserver;
21
+ observerInterval;
22
+ dbOplog = {};
23
+ isMonitoringEnabled = false;
24
+ dbStateCodeToName = {
25
+ [mongoose.ConnectionStates.disconnected]: "disconnected",
26
+ [mongoose.ConnectionStates.connected]: "connected",
27
+ [mongoose.ConnectionStates.connecting]: "connecting",
28
+ [mongoose.ConnectionStates.disconnecting]: "disconnecting"
29
+ };
30
+ /**
31
+ * https://www.mongodb.com/docs/manual/core/transactions/#read-concern-write-concern-read-preference
32
+ */
33
+ defaultTransactionOptions = { readPreference: "primary" };
34
+ constructor(databaseAdapter, dbConfigs, envService, logger) {
35
+ this.databaseAdapter = databaseAdapter;
36
+ this.dbConfigs = dbConfigs;
37
+ this.envService = envService;
38
+ this.logger = logger;
39
+ this.isMonitoringEnabled = this.dbConfigs.main?.metrics.enabled || false;
40
+ if (this.isMonitoringEnabled) {
41
+ this.dbOperationsHistogram = new Histogram("diia_mongodb_operation_seconds", mongodbOperationsAllowedFields, "Mongodb operation duration in seconds", this.dbConfigs.main?.metrics.buckets || mongodbOperationsDefaultBuckets);
42
+ this.dbOplogObserver = new Observer("diia_mongodb_oplog_cache_size", [], "Amount of operations stored in in-memory cache for monitoring");
43
+ }
44
+ }
45
+ async onInit() {
46
+ if (this.databaseAdapter !== "mongo") return;
47
+ const tasks = Object.entries(this.dbConfigs).map(async ([type, config]) => {
48
+ const dbType = type;
49
+ const connection = await this.createDbConnection(dbType, config);
50
+ if (connection) this.db[dbType] = connection;
51
+ });
52
+ await Promise.all(tasks);
53
+ if (this.isMonitoringEnabled) this.observerInterval = setInterval(() => {
54
+ this.oplogObserver();
55
+ }, 5e3);
56
+ }
57
+ async onDestroy() {
58
+ if (this.isMonitoringEnabled) clearInterval(this.observerInterval);
59
+ for (const db of Object.values(this.db)) await db.connection.close();
60
+ }
61
+ async onHealthCheck() {
62
+ const dbStatus = {};
63
+ for (const [type, db] of Object.entries(this.db)) {
64
+ const dbType = type;
65
+ try {
66
+ await db.connection.db?.listCollections({}, {
67
+ nameOnly: true,
68
+ authorizedCollections: true
69
+ }).toArray();
70
+ } catch (err) {
71
+ this.logger.error("Mongo list collections error", {
72
+ err,
73
+ dbType
74
+ });
75
+ dbStatus[dbType] = "op_failed";
76
+ continue;
77
+ }
78
+ dbStatus[dbType] = this.dbStateCodeToName[db.connection.readyState];
79
+ }
80
+ return {
81
+ status: Object.values(dbStatus).some((s) => s !== "connected") ? HttpStatusCode.SERVICE_UNAVAILABLE : HttpStatusCode.OK,
82
+ details: { mongodb: dbStatus }
83
+ };
84
+ }
85
+ async createDbConnection(type, config) {
86
+ const { isEnabled } = config;
87
+ if (typeof isEnabled === "boolean" && !isEnabled) {
88
+ this.logger.info(`Database is disabled: ${type}`);
89
+ return;
90
+ }
91
+ try {
92
+ const { host, port, database, authSource, user, password, replicaSet, replicaSetNodes, readPreference, authMechanism } = config;
93
+ const connectionOptions = {};
94
+ let hosts = [];
95
+ if (host) if (port) hosts.push(`${host}:${port}`);
96
+ else hosts.push(host);
97
+ if (user && password) connectionOptions.auth = {
98
+ username: user,
99
+ password
100
+ };
101
+ if (replicaSet) connectionOptions.replicaSet = replicaSet;
102
+ if (replicaSetNodes) {
103
+ if (host) {
104
+ const errMsg = "Must be only `host` and `port` or `replicaSetNodes` config";
105
+ this.logger.error("Wrong database configuration:", errMsg);
106
+ throw new Error(errMsg);
107
+ }
108
+ hosts = replicaSetNodes.map(({ replicaHost }) => `${replicaHost}:${port}`);
109
+ }
110
+ let connectionString = `mongodb://${hosts.join(",")}/`;
111
+ if (database) connectionOptions.dbName = database;
112
+ const query = [];
113
+ if (authSource) query.push(`authSource=${authSource}`);
114
+ if (readPreference) query.push(`readPreference=${readPreference}`);
115
+ if (authMechanism) query.push(`authMechanism=${authMechanism}`);
116
+ if (query.length > 0) connectionString += `?${query.join("&")}`;
117
+ const logOptions = cloneDeep(connectionOptions);
118
+ if (logOptions.auth) logOptions.auth = {
119
+ username: "********",
120
+ password: "********"
121
+ };
122
+ this.logger.info(`Connecting to DB ${connectionString} ${type}`, logOptions);
123
+ if (this.envService.isLocal() || this.envService.isTest()) {
124
+ this.logger.debug("Mongoose set to Debug");
125
+ mongoose.set("debug", (coll, method, dbQuery, doc, options) => {
126
+ this.logger.debug("Mongo: ", {
127
+ coll,
128
+ method,
129
+ query: dbQuery,
130
+ doc,
131
+ options
132
+ });
133
+ });
134
+ }
135
+ let connection;
136
+ if (type === "main") {
137
+ await mongoose.connect(connectionString, {
138
+ ...connectionOptions,
139
+ monitorCommands: this.isMonitoringEnabled
140
+ });
141
+ connection = mongoose.connection;
142
+ } else connection = await mongoose.createConnection(connectionString, connectionOptions).asPromise();
143
+ if (this.isMonitoringEnabled) this.connectMonitor(connection);
144
+ connection.on("error", (err) => {
145
+ this.logger.error("Mongo connection error", {
146
+ err,
147
+ type
148
+ });
149
+ });
150
+ return {
151
+ connection,
152
+ connectionString,
153
+ connectionOptions
154
+ };
155
+ } catch (err) {
156
+ this.logger.error("Failed to connect to Database", {
157
+ type,
158
+ err
159
+ });
160
+ throw new DatabaseError("Failed to connect to Database");
161
+ }
162
+ }
163
+ /**
164
+ * Begins a new MongoDB transaction by starting a session and transaction
165
+ *
166
+ * @param options - Optional transaction options to override the default transaction settings
167
+ * @returns A ClientSession with an active transaction
168
+ * @throws {DatabaseError} If the database connection is undefined or transaction fails to start
169
+ *
170
+ * @remarks
171
+ * - Uses the main database connection to start the session
172
+ * - Applies default transaction options (readPreference: primary)
173
+ * - Will abort transaction and end session if any errors occur during setup
174
+ */
175
+ async beginTransaction(options) {
176
+ const connection = this.db["main"]?.connection;
177
+ if (!connection) throw new DatabaseError("Connection is undefined");
178
+ const session = await connection.startSession();
179
+ try {
180
+ const transactionOptions = {
181
+ ...this.defaultTransactionOptions,
182
+ ...options
183
+ };
184
+ session.startTransaction(transactionOptions);
185
+ return session;
186
+ } catch (err) {
187
+ await session.abortTransaction();
188
+ await session.endSession();
189
+ throw new DatabaseError("Unable to begin transaction", { err });
190
+ }
191
+ }
192
+ connectMonitor(connection) {
193
+ try {
194
+ const client = connection.getClient();
195
+ client.on("commandStarted", (event) => {
196
+ const db = event.databaseName;
197
+ const opType = event.commandName;
198
+ const key = `${event.requestId}${event.connectionId}`;
199
+ const parsedCollection = Number.parseInt(event.command[opType] || "", 10);
200
+ const collection = Number.isNaN(parsedCollection) ? event.command[opType] : void 0;
201
+ this.dbOplog[key] = {
202
+ database: db,
203
+ collection,
204
+ opType
205
+ };
206
+ this.logger.debug("Mongodb commandStarted operation", {
207
+ database: db,
208
+ collection,
209
+ operation: opType,
210
+ command: event.command
211
+ });
212
+ });
213
+ client.on("commandSucceeded", (event) => {
214
+ const key = `${event.requestId}${event.connectionId}`;
215
+ const oplogEntry = this.dbOplog[key];
216
+ const durationMs = event.duration;
217
+ delete this.dbOplog[key];
218
+ this.logger.debug("Mongodb commandSucceeded operation", {
219
+ database: oplogEntry.database,
220
+ operation: oplogEntry.opType,
221
+ collection: oplogEntry.collection
222
+ });
223
+ this.dbOperationsHistogram?.observe({
224
+ operation: oplogEntry.opType,
225
+ status: "successful",
226
+ database: oplogEntry.database,
227
+ ...oplogEntry.collection && { collection: oplogEntry.collection }
228
+ }, durationMs / 1e3);
229
+ });
230
+ client.on("commandFailed", (event) => {
231
+ const key = `${event.requestId}${event.connectionId}`;
232
+ const oplogEntry = this.dbOplog[key];
233
+ const durationMs = event.duration;
234
+ delete this.dbOplog[key];
235
+ this.logger.debug("Mongodb commandFailed operation", {
236
+ database: oplogEntry.database,
237
+ operation: oplogEntry.opType,
238
+ collection: oplogEntry.collection
239
+ });
240
+ this.dbOperationsHistogram?.observe({
241
+ operation: oplogEntry.opType,
242
+ status: "failed",
243
+ database: oplogEntry.database,
244
+ ...oplogEntry.collection && { collection: oplogEntry.collection }
245
+ }, durationMs / 1e3);
246
+ });
247
+ } catch (err) {
248
+ this.logger.error("Couldn't attach commands monitor", { err });
249
+ }
250
+ }
251
+ oplogObserver() {
252
+ this.dbOplogObserver?.observe({}, Object.keys(this.dbOplog).length);
253
+ }
4
254
  };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DatabaseService = void 0;
7
- const lodash_1 = require("lodash");
8
- const mongoose_1 = __importDefault(require("mongoose"));
9
- const diia_metrics_1 = require("@diia-inhouse/diia-metrics");
10
- const errors_1 = require("@diia-inhouse/errors");
11
- const types_1 = require("@diia-inhouse/types");
12
- const interfaces_1 = require("../interfaces");
13
- class DatabaseService {
14
- databaseAdapter;
15
- dbConfigs;
16
- envService;
17
- logger;
18
- defaultModelsDir = 'models';
19
- db = {};
20
- dbOperationsHistogram;
21
- dbOplogObserver;
22
- observerInterval;
23
- dbOplog = {};
24
- isMonitoringEnabled = false;
25
- dbStateCodeToName = {
26
- [mongoose_1.default.ConnectionStates.disconnected]: interfaces_1.DbConnectionStatus.Disconnected,
27
- [mongoose_1.default.ConnectionStates.connected]: interfaces_1.DbConnectionStatus.Connected,
28
- [mongoose_1.default.ConnectionStates.connecting]: interfaces_1.DbConnectionStatus.Connecting,
29
- [mongoose_1.default.ConnectionStates.disconnecting]: interfaces_1.DbConnectionStatus.Disconnecting,
30
- };
31
- /**
32
- * https://www.mongodb.com/docs/manual/core/transactions/#read-concern-write-concern-read-preference
33
- */
34
- defaultTransactionOptions = {
35
- readPreference: 'primary',
36
- };
37
- constructor(databaseAdapter, dbConfigs, envService, logger) {
38
- this.databaseAdapter = databaseAdapter;
39
- this.dbConfigs = dbConfigs;
40
- this.envService = envService;
41
- this.logger = logger;
42
- this.isMonitoringEnabled = this.dbConfigs.main?.metrics.enabled || false;
43
- if (this.isMonitoringEnabled) {
44
- this.dbOperationsHistogram = new diia_metrics_1.Histogram('diia_mongodb_operation_seconds', interfaces_1.mongodbOperationsAllowedFields, 'Mongodb operation duration in seconds', this.dbConfigs.main?.metrics.buckets || interfaces_1.mongodbOperationsDefaultBuckets);
45
- this.dbOplogObserver = new diia_metrics_1.Observer('diia_mongodb_oplog_cache_size', [], 'Amount of operations stored in in-memory cache for monitoring');
46
- }
47
- }
48
- async onInit() {
49
- if (this.databaseAdapter !== 'mongo') {
50
- return;
51
- }
52
- const tasks = Object.entries(this.dbConfigs).map(async ([type, config]) => {
53
- const dbType = type;
54
- const connection = await this.createDbConnection(dbType, config);
55
- if (connection) {
56
- this.db[dbType] = connection;
57
- }
58
- });
59
- await Promise.all(tasks);
60
- if (this.isMonitoringEnabled) {
61
- this.observerInterval = setInterval(() => {
62
- this.oplogObserver();
63
- }, 5000);
64
- }
65
- }
66
- async onDestroy() {
67
- if (this.isMonitoringEnabled) {
68
- clearInterval(this.observerInterval);
69
- }
70
- for (const db of Object.values(this.db)) {
71
- await db.connection.close();
72
- }
73
- }
74
- async onHealthCheck() {
75
- const dbStatus = {};
76
- for (const [type, db] of Object.entries(this.db)) {
77
- const dbType = type;
78
- try {
79
- await db.connection.db
80
- ?.listCollections({}, {
81
- nameOnly: true,
82
- authorizedCollections: true,
83
- })
84
- .toArray();
85
- }
86
- catch (err) {
87
- this.logger.error('Mongo list collections error', { err, dbType });
88
- dbStatus[dbType] = interfaces_1.DbConnectionStatus.OpFailed;
89
- continue;
90
- }
91
- dbStatus[dbType] = this.dbStateCodeToName[db.connection.readyState];
92
- }
93
- const status = Object.values(dbStatus).some((s) => s !== interfaces_1.DbConnectionStatus.Connected)
94
- ? types_1.HttpStatusCode.SERVICE_UNAVAILABLE
95
- : types_1.HttpStatusCode.OK;
96
- return {
97
- status,
98
- details: { mongodb: dbStatus },
99
- };
100
- }
101
- async createDbConnection(type, config) {
102
- const { isEnabled } = config;
103
- if (typeof isEnabled === 'boolean' && !isEnabled) {
104
- this.logger.info(`Database is disabled: ${type}`);
105
- return;
106
- }
107
- try {
108
- const { host, port, database, authSource, user, password, replicaSet, replicaSetNodes, readPreference, authMechanism } = config;
109
- const connectionOptions = {};
110
- let hosts = [];
111
- if (host) {
112
- if (port) {
113
- hosts.push(`${host}:${port}`);
114
- }
115
- else {
116
- hosts.push(`${host}`);
117
- }
118
- }
119
- if (user && password) {
120
- connectionOptions.auth = { username: user, password };
121
- }
122
- if (replicaSet) {
123
- connectionOptions.replicaSet = replicaSet;
124
- }
125
- if (replicaSetNodes) {
126
- if (host) {
127
- const errMsg = 'Must be only `host` and `port` or `replicaSetNodes` config';
128
- this.logger.error('Wrong database configuration:', errMsg);
129
- throw new Error(errMsg);
130
- }
131
- hosts = replicaSetNodes.map(({ replicaHost }) => `${replicaHost}:${port}`);
132
- }
133
- let connectionString = `mongodb://${hosts.join(',')}/`;
134
- if (database) {
135
- connectionOptions.dbName = database;
136
- }
137
- const query = [];
138
- if (authSource) {
139
- query.push(`authSource=${authSource}`);
140
- }
141
- if (readPreference) {
142
- query.push(`readPreference=${readPreference}`);
143
- }
144
- if (authMechanism) {
145
- query.push(`authMechanism=${authMechanism}`);
146
- }
147
- if (query.length > 0) {
148
- connectionString += `?${query.join('&')}`;
149
- }
150
- const logOptions = (0, lodash_1.cloneDeep)(connectionOptions);
151
- if (logOptions.auth) {
152
- logOptions.auth = {
153
- username: '********',
154
- password: '********',
155
- };
156
- }
157
- this.logger.info(`Connecting to DB ${connectionString} ${type}`, logOptions);
158
- if (this.envService.isLocal() || this.envService.isTest()) {
159
- this.logger.debug('Mongoose set to Debug');
160
- mongoose_1.default.set('debug', (coll, method, dbQuery, doc, options) => {
161
- this.logger.debug('Mongo: ', { coll, method, query: dbQuery, doc, options });
162
- });
163
- }
164
- let connection;
165
- if (type === interfaces_1.DbType.Main) {
166
- await mongoose_1.default.connect(connectionString, { ...connectionOptions, monitorCommands: this.isMonitoringEnabled });
167
- connection = mongoose_1.default.connection;
168
- }
169
- else {
170
- connection = await mongoose_1.default.createConnection(connectionString, connectionOptions).asPromise();
171
- }
172
- if (this.isMonitoringEnabled) {
173
- this.connectMonitor(connection);
174
- }
175
- connection.on('error', (err) => {
176
- this.logger.error('Mongo connection error', { err, type });
177
- });
178
- return { connection, connectionString, connectionOptions };
179
- }
180
- catch (err) {
181
- this.logger.error('Failed to connect to Database', { type, err });
182
- throw new errors_1.DatabaseError('Failed to connect to Database');
183
- }
184
- }
185
- /**
186
- * Begins a new MongoDB transaction by starting a session and transaction
187
- *
188
- * @param options - Optional transaction options to override the default transaction settings
189
- * @returns A ClientSession with an active transaction
190
- * @throws {DatabaseError} If the database connection is undefined or transaction fails to start
191
- *
192
- * @remarks
193
- * - Uses the main database connection to start the session
194
- * - Applies default transaction options (readPreference: primary)
195
- * - Will abort transaction and end session if any errors occur during setup
196
- */
197
- async beginTransaction(options) {
198
- const connection = this.db[interfaces_1.DbType.Main]?.connection;
199
- if (!connection) {
200
- throw new errors_1.DatabaseError('Connection is undefined');
201
- }
202
- const session = await connection.startSession();
203
- try {
204
- const transactionOptions = {
205
- ...this.defaultTransactionOptions,
206
- ...options,
207
- };
208
- session.startTransaction(transactionOptions);
209
- return session;
210
- }
211
- catch (err) {
212
- await session.abortTransaction();
213
- await session.endSession();
214
- throw new errors_1.DatabaseError('Unable to begin transaction', { err });
215
- }
216
- }
217
- connectMonitor(connection) {
218
- try {
219
- const client = connection.getClient();
220
- client.on('commandStarted', (event) => {
221
- const db = event.databaseName;
222
- const opType = event.commandName;
223
- const key = `${event.requestId}${event.connectionId}`;
224
- const parsedCollection = Number.parseInt(event.command[opType] || '', 10);
225
- const collection = Number.isNaN(parsedCollection) ? event.command[opType] : undefined;
226
- this.dbOplog[key] = {
227
- database: db,
228
- collection,
229
- opType,
230
- };
231
- this.logger.debug('Mongodb commandStarted operation', {
232
- database: db,
233
- collection,
234
- operation: opType,
235
- command: event.command,
236
- });
237
- });
238
- client.on('commandSucceeded', (event) => {
239
- const key = `${event.requestId}${event.connectionId}`;
240
- const oplogEntry = this.dbOplog[key];
241
- const durationMs = event.duration;
242
- delete this.dbOplog[key];
243
- this.logger.debug('Mongodb commandSucceeded operation', {
244
- database: oplogEntry.database,
245
- operation: oplogEntry.opType,
246
- collection: oplogEntry.collection,
247
- });
248
- this.dbOperationsHistogram?.observe({
249
- operation: oplogEntry.opType,
250
- status: interfaces_1.MongodbOperationResult.Successful,
251
- database: oplogEntry.database,
252
- ...(oplogEntry.collection && { collection: oplogEntry.collection }),
253
- }, durationMs / 1000);
254
- });
255
- client.on('commandFailed', (event) => {
256
- const key = `${event.requestId}${event.connectionId}`;
257
- const oplogEntry = this.dbOplog[key];
258
- const durationMs = event.duration;
259
- delete this.dbOplog[key];
260
- this.logger.debug('Mongodb commandFailed operation', {
261
- database: oplogEntry.database,
262
- operation: oplogEntry.opType,
263
- collection: oplogEntry.collection,
264
- });
265
- this.dbOperationsHistogram?.observe({
266
- operation: oplogEntry.opType,
267
- status: interfaces_1.MongodbOperationResult.Failed,
268
- database: oplogEntry.database,
269
- ...(oplogEntry.collection && { collection: oplogEntry.collection }),
270
- }, durationMs / 1000);
271
- });
272
- }
273
- catch (err) {
274
- this.logger.error("Couldn't attach commands monitor", { err });
275
- }
276
- }
277
- oplogObserver() {
278
- this.dbOplogObserver?.observe({}, Object.keys(this.dbOplog).length);
279
- }
280
- }
281
- exports.DatabaseService = DatabaseService;
282
- //# sourceMappingURL=database.js.map
255
+ //#endregion
256
+ export { DatabaseService };
@@ -0,0 +1,73 @@
1
+ import { DatabaseAdapterType, PostgresDatabase } from "../interfaces/database.js";
2
+ import { EncryptedStorage } from "../interfaces/models/encryptedStorage.js";
3
+ import { Model, QueryOptions } from "mongoose";
4
+ import { Logger } from "@diia-inhouse/types";
5
+ import { EnvService } from "@diia-inhouse/env";
6
+ import { AuthService } from "@diia-inhouse/crypto";
7
+
8
+ //#region src/services/encryptedStorage.d.ts
9
+ /**
10
+ * EncryptedStorageService provides CRUD operations for managing encrypted storage entries
11
+ * in both Postgres and MongoDB databases. The service abstracts database implementation details
12
+ * using a strategy pattern depending on the configured database adapter type.
13
+ *
14
+ * Suitable for storing and managing sensitive data in an encrypted manner with specific expiration time.
15
+ *
16
+ * This service should be wrapped in a repository.
17
+ *
18
+ * Register this service in the DI container with the following (MongoDB) configuration (AuthService is located in @diia-inhouse/crypto):
19
+ * ```typescript
20
+ * // src/deps.ts
21
+ * encryptedStorage: asClass(EncryptedStorageService, { injector: () => ({ encryptedStorageModel }) }).singleton(),
22
+ * auth: asClass(AuthService, { injector: () => ({ authConfig: config.auth }) }).singleton()
23
+ * ```
24
+ *
25
+ * ```typescript
26
+ * // src/config.ts
27
+ * auth: {
28
+ * jwk: await envService.getSecret('JWE_SECRET_DATA_JWK'),
29
+ * }
30
+ * ```
31
+ *
32
+ * ```typescript
33
+ * // src/models/encryptedStorage.ts
34
+ * import { EncryptedStorage, Model, encryptedStorageSchema, model, models } from '@diia-inhouse/db'
35
+ *
36
+ * export default (models.EncryptedStorage as Model<EncryptedStorage>) || model('EncryptedStorage', encryptedStorageSchema)
37
+ * ```
38
+ *
39
+ * Use the following command to generate a JWE secret data JWK:
40
+ * node -e "const crypto = require('crypto'); const { privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); const jwk = privateKey.export({ format: 'jwk' }); jwk.use = 'enc'; console.log(JSON.stringify(jwk));"
41
+ * ```typescript
42
+ * // .env.example
43
+ * JWE_SECRET_DATA_JWK={your-jwk-here}
44
+ * ```
45
+ */
46
+ declare class EncryptedStorageService {
47
+ private readonly logger;
48
+ private readonly auth;
49
+ private readonly envService;
50
+ private readonly databaseAdapter;
51
+ private readonly encryptedStorageModel;
52
+ private readonly postgresDatabase;
53
+ private readonly saveEntityStrategy;
54
+ private readonly updateEntityStrategy;
55
+ private readonly findEntityStrategy;
56
+ private readonly deleteManyStrategy;
57
+ private readonly removeEntityStrategy;
58
+ private readonly findExpirationStrategy;
59
+ constructor(logger: Logger, auth: AuthService, envService: EnvService, databaseAdapter: DatabaseAdapterType, encryptedStorageModel?: Model<EncryptedStorage> | null, postgresDatabase?: PostgresDatabase | null);
60
+ save<T>(data: T, expiration: number): Promise<string>;
61
+ get<T>(id: string, options?: QueryOptions): Promise<T>;
62
+ getSafe<T>(id: string, options?: QueryOptions): Promise<T | undefined>;
63
+ update<T>(id: string, data: T): Promise<void>;
64
+ remove(id: string): Promise<void>;
65
+ deleteMany(ids: string[]): Promise<void>;
66
+ setExpiration(id: string, expiration: number): Promise<void>;
67
+ getExpiration(id: string): Promise<Date>;
68
+ private getStorageItemById;
69
+ private getModel;
70
+ private getPostgresDatabase;
71
+ }
72
+ //#endregion
73
+ export { EncryptedStorageService };