@tmlmobilidade/databases 20260324.1249.38 → 20260324.1728.13

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.
@@ -42,7 +42,14 @@ export class GOMongoClient {
42
42
  async connect() {
43
43
  Logger.info('[GOMongoClient] Connecting to database...');
44
44
  const connectionString = await this.getConnectionString();
45
- this.client = new MongoClient(connectionString);
45
+ this.client = new MongoClient(connectionString, {
46
+ connectTimeoutMS: 10_000,
47
+ directConnection: process.env.GO_MONGO_TUNNEL_ENABLED === 'true',
48
+ maxPoolSize: 20,
49
+ minPoolSize: 2,
50
+ readPreference: 'secondaryPreferred',
51
+ serverSelectionTimeoutMS: 10_000,
52
+ });
46
53
  this.client.on('close', () => {
47
54
  console.warn('[GOMongoClient] Database connection closed unexpectedly.');
48
55
  });
@@ -42,7 +42,14 @@ export class PCGIFileManagerClient {
42
42
  async connect() {
43
43
  Logger.info('[PCGIFileManagerClient] Connecting to database...');
44
44
  const connectionString = await this.getConnectionString();
45
- this.client = new MongoClient(connectionString);
45
+ this.client = new MongoClient(connectionString, {
46
+ connectTimeoutMS: 10_000,
47
+ directConnection: process.env.PCGI_FILE_MANAGER_TUNNEL_ENABLED === 'true',
48
+ maxPoolSize: 20,
49
+ minPoolSize: 2,
50
+ readPreference: 'secondaryPreferred',
51
+ serverSelectionTimeoutMS: 10_000,
52
+ });
46
53
  this.client.on('close', () => {
47
54
  console.warn('[PCGIFileManagerClient] Database connection closed unexpectedly.');
48
55
  });
@@ -42,7 +42,14 @@ export class PCGIRawClient {
42
42
  async connect() {
43
43
  Logger.info('[PCGIRawClient] Connecting to database...');
44
44
  const connectionString = await this.getConnectionString();
45
- this.client = new MongoClient(connectionString);
45
+ this.client = new MongoClient(connectionString, {
46
+ connectTimeoutMS: 10_000,
47
+ directConnection: process.env.PCGI_RAW_TUNNEL_ENABLED === 'true',
48
+ maxPoolSize: 20,
49
+ minPoolSize: 2,
50
+ readPreference: 'secondaryPreferred',
51
+ serverSelectionTimeoutMS: 10_000,
52
+ });
46
53
  this.client.on('close', () => {
47
54
  console.warn('[PCGIRawClient] Database connection closed unexpectedly.');
48
55
  });
@@ -42,7 +42,14 @@ export class PCGITicketingClient {
42
42
  async connect() {
43
43
  Logger.info('[PCGITicketingClient] Connecting to database...');
44
44
  const connectionString = await this.getConnectionString();
45
- this.client = new MongoClient(connectionString);
45
+ this.client = new MongoClient(connectionString, {
46
+ connectTimeoutMS: 10_000,
47
+ directConnection: process.env.PCGI_TICKETING_TUNNEL_ENABLED === 'true',
48
+ maxPoolSize: 20,
49
+ minPoolSize: 2,
50
+ readPreference: 'secondaryPreferred',
51
+ serverSelectionTimeoutMS: 10_000,
52
+ });
46
53
  this.client.on('close', () => {
47
54
  console.warn('[PCGITicketingClient] Database connection closed unexpectedly.');
48
55
  });
@@ -42,7 +42,14 @@ export class PCGIValidationsClient {
42
42
  async connect() {
43
43
  Logger.info('[PCGIValidationsClient] Connecting to database...');
44
44
  const connectionString = await this.getConnectionString();
45
- this.client = new MongoClient(connectionString);
45
+ this.client = new MongoClient(connectionString, {
46
+ connectTimeoutMS: 10_000,
47
+ directConnection: process.env.PCGI_VALIDATIONS_TUNNEL_ENABLED === 'true',
48
+ maxPoolSize: 20,
49
+ minPoolSize: 2,
50
+ readPreference: 'secondaryPreferred',
51
+ serverSelectionTimeoutMS: 10_000,
52
+ });
46
53
  this.client.on('close', () => {
47
54
  console.warn('[PCGIValidationsClient] Database connection closed unexpectedly.');
48
55
  });
@@ -4,7 +4,7 @@ import { type RawVehicleEvent } from '@tmlmobilidade/types';
4
4
  declare class RawVehicleEventsNewClass extends MongoInterfaceTemplate<RawVehicleEvent, RawVehicleEvent, Partial<RawVehicleEvent>> {
5
5
  private static _instance;
6
6
  protected readonly collectionName = "raw_vehicle_events";
7
- protected readonly databaseName = "raw_vehicle_events";
7
+ protected readonly databaseName = "raw";
8
8
  protected readonly indexDescription: SimplifiedMongoIndex<RawVehicleEvent>[];
9
9
  protected createSchema: import("zod").ZodDiscriminatedUnion<"version", [import("zod").ZodObject<{
10
10
  _id: import("zod").ZodString;
@@ -8,7 +8,7 @@ class RawVehicleEventsNewClass extends MongoInterfaceTemplate {
8
8
  //
9
9
  static _instance = null;
10
10
  collectionName = 'raw_vehicle_events';
11
- databaseName = 'raw_vehicle_events';
11
+ databaseName = 'raw';
12
12
  indexDescription = [
13
13
  { key: { agency_id: 1, created_at: 1 } },
14
14
  ];
@@ -119,6 +119,12 @@ export declare abstract class MongoInterfaceTemplate<T extends Document, TCreate
119
119
  * indexes, materialized views, constraints, etc.
120
120
  */
121
121
  protected postInit(): Promise<void>;
122
+ /**
123
+ * Ensures that the specified collection exists in the MongoDB database,
124
+ * creating it if it does not already exist.
125
+ * @returns A promise that resolves when the collection is ensured to exist.
126
+ */
127
+ private createCollectionIfNotExists;
122
128
  /**
123
129
  * Ensures that the specified indexes exist in MongoDB, creating them if they do not already exist.
124
130
  * This method performs input validation to prevent unsafe operations and logs the outcome of the operation.
@@ -1,5 +1,5 @@
1
1
  /* * */
2
- import { isSameIndex, normalizeMongoIndex } from '../utils/mongo/index.js';
2
+ import { isSameIndex, prepareMongoIndexOptions } from '../utils/mongo/index.js';
3
3
  import { Logger } from '@tmlmobilidade/logger';
4
4
  /* * */
5
5
  export class MongoInterfaceTemplate {
@@ -177,7 +177,9 @@ export class MongoInterfaceTemplate {
177
177
  this.client = await this.connectToClient();
178
178
  this.database = this.client.db(this.databaseName);
179
179
  this.collection = this.database.collection(this.collectionName);
180
- // Ensure the collection indexes are in sync with the provided index description
180
+ // Ensure the collection exists and its indexes are in sync
181
+ // with the provided index description.
182
+ await this.createCollectionIfNotExists();
181
183
  await this.syncIndexes();
182
184
  // Call postInit for any additional setup logic defined in subclasses
183
185
  await this.postInit();
@@ -189,6 +191,18 @@ export class MongoInterfaceTemplate {
189
191
  async postInit() {
190
192
  // no-op by default
191
193
  }
194
+ /**
195
+ * Ensures that the specified collection exists in the MongoDB database,
196
+ * creating it if it does not already exist.
197
+ * @returns A promise that resolves when the collection is ensured to exist.
198
+ */
199
+ async createCollectionIfNotExists() {
200
+ const collections = await this.database.listCollections({ name: this.collectionName }).toArray();
201
+ if (collections.length)
202
+ return;
203
+ await this.database.createCollection(this.collectionName);
204
+ Logger.info(`MONGODB [${this.collectionName}]: Collection created.`);
205
+ }
192
206
  /**
193
207
  * Ensures that the specified indexes exist in MongoDB, creating them if they do not already exist.
194
208
  * This method performs input validation to prevent unsafe operations and logs the outcome of the operation.
@@ -198,12 +212,11 @@ export class MongoInterfaceTemplate {
198
212
  */
199
213
  async syncIndexes() {
200
214
  try {
215
+ Logger.info(`MONGODB [${this.collectionName}]: Synchronizing indexes...`);
201
216
  // Normalize already applied and new indexes
202
217
  // and filter the default _id index.
203
218
  const existingIndexes = await this.collection.indexes();
204
- const normalizedExisting = existingIndexes.map((normalizeMongoIndex));
205
- const filteredExisting = normalizedExisting.filter(idx => JSON.stringify(idx.key) !== JSON.stringify({ _id: 1 }));
206
- const normalizedDesired = this.indexDescription.map(idx => normalizeMongoIndex(idx));
219
+ const filteredExisting = existingIndexes.filter(idx => JSON.stringify(idx.key) !== JSON.stringify({ _id: 1 }));
207
220
  // Setup desired indexes based on indexDescription
208
221
  const indexesToDrop = [];
209
222
  const indexesToCreate = [];
@@ -211,13 +224,13 @@ export class MongoInterfaceTemplate {
211
224
  for (const existingIdx of filteredExisting) {
212
225
  // For the list of existing indexes,
213
226
  // check if they are present in the desired index description.
214
- const found = normalizedDesired.some(desiredIdx => isSameIndex(existingIdx, desiredIdx));
227
+ const found = this.indexDescription.some(desiredIdx => isSameIndex(existingIdx, desiredIdx));
215
228
  // If not, mark them for dropping.
216
229
  if (!found)
217
- indexesToDrop.push(existingIdx);
230
+ indexesToDrop.push(existingIdx.name);
218
231
  }
219
232
  // Find indexes to create
220
- for (const desiredIdx of normalizedDesired) {
233
+ for (const desiredIdx of this.indexDescription) {
221
234
  // For the list of desired indexes,
222
235
  // check if they are present in the existing indexes.
223
236
  const found = filteredExisting.some(existingIdx => isSameIndex(existingIdx, desiredIdx));
@@ -226,20 +239,20 @@ export class MongoInterfaceTemplate {
226
239
  indexesToCreate.push(desiredIdx);
227
240
  }
228
241
  // Drop indexes
229
- for (const idx of indexesToDrop) {
230
- if (!idx.name)
242
+ for (const idxName of indexesToDrop) {
243
+ if (!idxName)
231
244
  continue;
232
- await this.collection.dropIndex(idx.name);
245
+ Logger.info(`MONGODB [${this.collectionName}]: Dropping index ${idxName}.`);
246
+ await this.collection.dropIndex(idxName);
247
+ Logger.success(`MONGODB [${this.collectionName}]: Dropped index ${idxName}.`);
233
248
  }
234
249
  // Create indexes
235
250
  for (const idx of indexesToCreate) {
236
- await this.collection.createIndex(idx.key, {
237
- expireAfterSeconds: idx.expireAfterSeconds ?? undefined,
238
- sparse: idx.sparse,
239
- unique: idx.unique,
240
- });
251
+ Logger.info(`MONGODB [${this.collectionName}]: Creating index on keys ${JSON.stringify(idx.key)} with options ${JSON.stringify(prepareMongoIndexOptions(idx))}.`);
252
+ await this.collection.createIndex(idx.key, prepareMongoIndexOptions(idx));
253
+ Logger.success(`MONGODB [${this.collectionName}]: Created index on keys ${JSON.stringify(idx.key)}.`);
241
254
  }
242
- Logger.info(`MONGODB [${this.collectionName}]: Indexes synchronized.`);
255
+ Logger.success(`MONGODB [${this.collectionName}]: Indexes synchronized.`);
243
256
  }
244
257
  catch (error) {
245
258
  Logger.error(`MONGODB [${this.collectionName}]: Error @ syncIndexes(): ${error.message}`);
@@ -9,10 +9,3 @@ export interface SimplifiedMongoIndex<T> {
9
9
  sparse?: boolean;
10
10
  unique?: boolean;
11
11
  }
12
- /**
13
- * A MongoDB index description type that is normalized
14
- * to ensure all expected properties are present and have consistent types.
15
- */
16
- export type ComparableMongoIndex<T> = Required<SimplifiedMongoIndex<T>> & {
17
- name?: string;
18
- };
@@ -1,12 +1,13 @@
1
- import { type ComparableMongoIndex, type SimplifiedMongoIndex } from '../../types/mongo/index-description.js';
2
- import { type IndexDescriptionInfo } from 'mongodb';
1
+ import { type SimplifiedMongoIndex } from '../../types/mongo/index-description.js';
2
+ import { type CreateIndexesOptions, type IndexDescriptionInfo } from 'mongodb';
3
3
  /**
4
- * Normalizes a MongoDB index description by ensuring that
5
- * all expected properties are present and have consistent types.
6
- * @param indexDescription The original index description from MongoDB.
7
- * @returns A normalized index description with default values for missing properties.
4
+ * Prepares MongoDB index options by extracting relevant properties
5
+ * from a simplified index description. This function ensures that
6
+ * only valid options are included when creating indexes in MongoDB.
7
+ * @param idx A simplified index description defined by the user.
8
+ * @returns An object containing valid MongoDB index options based on the provided description.
8
9
  */
9
- export declare function normalizeMongoIndex<T>(indexDescription: IndexDescriptionInfo | SimplifiedMongoIndex<T>): ComparableMongoIndex<T>;
10
+ export declare function prepareMongoIndexOptions<T>(idx: SimplifiedMongoIndex<T>): CreateIndexesOptions;
10
11
  /**
11
12
  * Compares two normalized MongoDB index descriptions for equivalence.
12
13
  * Two indexes are considered the same if they have identical key definitions
@@ -15,4 +16,4 @@ export declare function normalizeMongoIndex<T>(indexDescription: IndexDescriptio
15
16
  * @param b The second normalized index description to compare.
16
17
  * @returns True if the indexes are considered the same, false otherwise.
17
18
  */
18
- export declare function isSameIndex<T>(a: ComparableMongoIndex<T>, b: ComparableMongoIndex<T>): boolean;
19
+ export declare function isSameIndex<T>(a: IndexDescriptionInfo | SimplifiedMongoIndex<T>, b: IndexDescriptionInfo | SimplifiedMongoIndex<T>): boolean;
@@ -1,13 +1,30 @@
1
1
  /* * */
2
+ /**
3
+ * Prepares MongoDB index options by extracting relevant properties
4
+ * from a simplified index description. This function ensures that
5
+ * only valid options are included when creating indexes in MongoDB.
6
+ * @param idx A simplified index description defined by the user.
7
+ * @returns An object containing valid MongoDB index options based on the provided description.
8
+ */
9
+ export function prepareMongoIndexOptions(idx) {
10
+ const result = {};
11
+ if (idx.expireAfterSeconds !== undefined)
12
+ result.expireAfterSeconds = idx.expireAfterSeconds;
13
+ if (idx.sparse !== undefined)
14
+ result.sparse = idx.sparse;
15
+ if (idx.unique !== undefined)
16
+ result.unique = idx.unique;
17
+ return result;
18
+ }
2
19
  /**
3
20
  * Normalizes a MongoDB index description by ensuring that
4
21
  * all expected properties are present and have consistent types.
5
- * @param indexDescription The original index description from MongoDB.
22
+ * @param indexDescription The existing indexes from MongoDB or a simplified structure defined by the user.
6
23
  * @returns A normalized index description with default values for missing properties.
7
24
  */
8
- export function normalizeMongoIndex(indexDescription) {
25
+ function normalizeMongoIndex(indexDescription) {
9
26
  return {
10
- expireAfterSeconds: indexDescription.expireAfterSeconds ?? null,
27
+ expireAfterSeconds: indexDescription.expireAfterSeconds ?? undefined,
11
28
  key: indexDescription.key,
12
29
  sparse: !!indexDescription.sparse,
13
30
  unique: !!indexDescription.unique,
@@ -22,14 +39,18 @@ export function normalizeMongoIndex(indexDescription) {
22
39
  * @returns True if the indexes are considered the same, false otherwise.
23
40
  */
24
41
  export function isSameIndex(a, b) {
42
+ // Normalize both index descriptions
43
+ // to ensure consistent comparison.
44
+ const normalizedA = normalizeMongoIndex(a);
45
+ const normalizedB = normalizeMongoIndex(b);
25
46
  // Sort keys to ensure consistent comparison regardless of order.
26
- const aKeySorted = Object.fromEntries(Object.entries(a.key).sort());
27
- const bKeySorted = Object.fromEntries(Object.entries(b.key).sort());
47
+ const aKeySorted = Object.fromEntries(Object.entries(normalizedA.key).sort());
48
+ const bKeySorted = Object.fromEntries(Object.entries(normalizedB.key).sort());
28
49
  // Check if keys are the same and if unique/sparse settings match.
29
50
  const matchingKeys = JSON.stringify(aKeySorted) === JSON.stringify(bKeySorted);
30
- const matchingUnique = !!a.unique === !!b.unique;
31
- const matchingSparse = !!a.sparse === !!b.sparse;
32
- const matchingExpire = a.expireAfterSeconds === b.expireAfterSeconds;
51
+ const matchingUnique = !!normalizedA.unique === !!normalizedB.unique;
52
+ const matchingSparse = !!normalizedA.sparse === !!normalizedB.sparse;
53
+ const matchingExpire = normalizedA.expireAfterSeconds === normalizedB.expireAfterSeconds;
33
54
  // Consider indexes the same if all properties match.
34
55
  return matchingKeys && matchingUnique && matchingSparse && matchingExpire;
35
56
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tmlmobilidade/databases",
3
- "version": "20260324.1249.38",
3
+ "version": "20260324.1728.13",
4
4
  "author": {
5
5
  "email": "iso@tmlmobilidade.pt",
6
6
  "name": "TML-ISO"