@orion-js/mongodb 4.0.19 → 4.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -29,23 +29,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  // src/index.ts
30
30
  var index_exports = {};
31
31
  __export(index_exports, {
32
+ BaseCollection: () => BaseCollection,
32
33
  Collection: () => Collection,
34
+ ENCRYPTION_ALGORITHMS: () => ENCRYPTION_ALGORITHMS,
33
35
  MongoCollection: () => MongoCollection,
34
36
  MongoDB: () => MongoDB,
35
37
  Repository: () => Repository,
36
38
  allConnectionPromises: () => allConnectionPromises,
39
+ configureConnection: () => configureConnection,
37
40
  connections: () => connections,
38
41
  createCollection: () => createCollection,
39
42
  createIndexesPromises: () => createIndexesPromises,
40
43
  getMongoConnection: () => getMongoConnection,
44
+ getOrCreateEncryptionKey: () => getOrCreateEncryptionKey,
41
45
  typedId: () => typedId
42
46
  });
43
47
  module.exports = __toCommonJS(index_exports);
44
48
 
45
49
  // src/connect/connections.ts
46
- var connections = {};
47
-
48
- // src/connect/getMongoConnection.ts
50
+ var import_node_events = require("events");
49
51
  var import_mongodb = require("mongodb");
50
52
 
51
53
  // src/connect/getDBName.ts
@@ -68,10 +70,14 @@ function getDBName(url) {
68
70
  return dbName;
69
71
  }
70
72
 
73
+ // src/connect/connections.ts
74
+ var import_node_process = require("process");
75
+ var import_logger = require("@orion-js/logger");
76
+ var import_helpers = require("@orion-js/helpers");
77
+
71
78
  // src/connect/getMongoURLFromEnv.ts
72
79
  var import_env = require("@orion-js/env");
73
80
  var getMongoURLFromEnv = (connectionName) => {
74
- var _a;
75
81
  if (connectionName === "main") {
76
82
  if (!(0, import_env.internalGetEnv)("mongo_url", "MONGO_URL")) {
77
83
  throw new Error("MONGO_URL is required");
@@ -80,7 +86,7 @@ var getMongoURLFromEnv = (connectionName) => {
80
86
  }
81
87
  const envName = `mongo_url_${connectionName.toLowerCase()}`;
82
88
  const processEnvName = `MONGO_URL_${connectionName.toUpperCase()}`;
83
- const uri = ((_a = connections[connectionName]) == null ? void 0 : _a.uri) ?? (0, import_env.internalGetEnv)(envName, processEnvName);
89
+ const uri = (0, import_env.internalGetEnv)(envName, processEnvName);
84
90
  if (!uri) {
85
91
  throw new Error(
86
92
  `To use the connection "${connectionName}" you must initialize it first calling getMongoConnection({name: "${connectionName}", uri: "MONGOURI"}) or setting the environment variable ${processEnvName}.`
@@ -88,71 +94,174 @@ var getMongoURLFromEnv = (connectionName) => {
88
94
  }
89
95
  return uri;
90
96
  };
97
+ var requiresExplicitSetup = (connectionName) => {
98
+ if (connectionName === "main") {
99
+ const value2 = (0, import_env.internalGetEnv)("mongo_explicit_setup", "MONGO_EXPLICIT_SETUP");
100
+ return typeof value2 === "boolean" ? value2 : value2 === "true";
101
+ }
102
+ const envName = `mongo_explicit_setup_${connectionName.toLowerCase()}`;
103
+ const processEnvName = `MONGO_EXPLICIT_SETUP_${connectionName.toUpperCase()}`;
104
+ const value = (0, import_env.internalGetEnv)(envName, processEnvName);
105
+ return typeof value === "boolean" ? value : value === "true";
106
+ };
91
107
 
92
- // src/connect/getMongoConnection.ts
93
- var import_logger = require("@orion-js/logger");
94
- var import_helpers = require("@orion-js/helpers");
95
- var allConnectionPromises = [];
96
- async function connect(client) {
97
- try {
98
- const result = await client.connect();
99
- return result;
100
- } catch (error) {
101
- import_logger.logger.error(`Error connecting to mongo: ${error.message}. Will retry in 5s`, error);
102
- await (0, import_helpers.sleep)(5e3);
103
- return connect(client);
108
+ // src/connect/connections.ts
109
+ var connectionWrappers = {};
110
+ var OrionMongoDatabaseWrapper = class {
111
+ uri;
112
+ dbName;
113
+ connectionName;
114
+ connectionPromise;
115
+ mongoOptions;
116
+ connectionEvent = new import_node_events.EventEmitter();
117
+ state = "disconnected";
118
+ client;
119
+ db;
120
+ configured = false;
121
+ encrypted = { client: null, db: null };
122
+ configTimeout;
123
+ constructor(connectionName) {
124
+ this.connectionName = connectionName;
125
+ import_logger.logger.info("New connection requested", {
126
+ connectionName
127
+ });
128
+ this.connectionEvent.setMaxListeners(Number.POSITIVE_INFINITY);
129
+ this.connectionPromise = new Promise((resolve, reject) => {
130
+ if (this.state === "connected") {
131
+ resolve(this.client);
132
+ }
133
+ this.connectionEvent.once("connected", resolve);
134
+ this.connectionEvent.once("error", reject);
135
+ });
136
+ this.configTimeout = setTimeout(() => {
137
+ import_logger.logger.error(
138
+ "Connection was required but never configured, call configureConnection() or unset the env variable MONGO_EXPLICIT_SETUP",
139
+ {
140
+ connectionName
141
+ }
142
+ );
143
+ this.connectionEvent.emit("error", new Error("Connection was required but never configured"));
144
+ }, 30 * 1e3);
145
+ }
146
+ config(mongoURL, mongoOptions) {
147
+ var _a;
148
+ this.uri = mongoURL;
149
+ this.mongoOptions = mongoOptions;
150
+ this.configured = true;
151
+ if ((_a = this.mongoOptions) == null ? void 0 : _a.autoEncryption) {
152
+ this.encrypted.client = new import_mongodb.MongoClient(mongoURL, {
153
+ retryReads: true,
154
+ ...this.mongoOptions
155
+ });
156
+ this.encrypted.db = this.encrypted.client.db(getDBName(this.uri));
157
+ }
158
+ this.client = new import_mongodb.MongoClient(mongoURL, {
159
+ retryReads: true,
160
+ ...this.mongoOptions,
161
+ autoEncryption: null
162
+ });
163
+ this.db = this.client.db(getDBName(this.uri));
164
+ clearTimeout(this.configTimeout);
165
+ }
166
+ async awaitConnection() {
167
+ if (this.state === "connected") return this;
168
+ if (this.state === "connecting" || !this.configured) {
169
+ await this.connectionPromise;
170
+ return this;
171
+ }
172
+ this.state = "connecting";
173
+ const censoredURI = this.uri.replace(/\/\/.*:.*@/, "//");
174
+ import_logger.logger.info("Connecting to mongo", {
175
+ uri: censoredURI,
176
+ connectionName: this.connectionName
177
+ });
178
+ if (this.encrypted.client) {
179
+ await this.connectWithRetry(this.encrypted.client);
180
+ import_logger.logger.info("Successfully connected to encrypted mongo", {
181
+ uri: censoredURI,
182
+ connectionName: this.connectionName
183
+ });
184
+ }
185
+ await this.connectWithRetry(this.client);
186
+ this.state = "connected";
187
+ this.connectionEvent.emit("connected", this.client);
188
+ import_logger.logger.info("Successfully connected to mongo", {
189
+ uri: censoredURI,
190
+ connectionName: this.connectionName
191
+ });
192
+ (0, import_node_process.nextTick)(() => {
193
+ this.connectionEvent.removeAllListeners();
194
+ this.connectionEvent = null;
195
+ });
196
+ return this;
197
+ }
198
+ async connectWithRetry(client) {
199
+ try {
200
+ return await client.connect();
201
+ } catch (error) {
202
+ import_logger.logger.error("Error connecting to mongo. Will retry in 5s", {
203
+ error,
204
+ connectionName: this.connectionName
205
+ });
206
+ await (0, import_helpers.sleep)(5e3);
207
+ return this.connectWithRetry(client);
208
+ }
209
+ }
210
+ async startConnection() {
211
+ return this.awaitConnection().then(() => this.client);
104
212
  }
213
+ async closeConnection() {
214
+ var _a, _b, _c;
215
+ if (this.state === "disconnected" || this.state === "disconnecting") return;
216
+ this.state = "disconnecting";
217
+ await ((_a = this.client) == null ? void 0 : _a.close());
218
+ await ((_c = (_b = this.encrypted) == null ? void 0 : _b.client) == null ? void 0 : _c.close());
219
+ this.state = "disconnected";
220
+ }
221
+ };
222
+ function configureConnection(connectionName, mongoOptions) {
223
+ connectionWrappers[connectionName] = connectionWrappers[connectionName] || new OrionMongoDatabaseWrapper(connectionName);
224
+ const connectionWrapper = connectionWrappers[connectionName];
225
+ if (connectionWrapper.configured) {
226
+ throw new Error("Connection already configured");
227
+ }
228
+ connectionWrapper.config(getMongoURLFromEnv(connectionName), mongoOptions);
229
+ return connectionWrapper.awaitConnection();
105
230
  }
231
+ function getExistingConnection(connectionName) {
232
+ connectionWrappers[connectionName] = connectionWrappers[connectionName] || new OrionMongoDatabaseWrapper(connectionName);
233
+ return connectionWrappers[connectionName];
234
+ }
235
+ var connections = connectionWrappers;
236
+
237
+ // src/connect/getMongoConnection.ts
238
+ var allConnectionPromises = [];
106
239
  var getMongoConnection = ({ name, uri }) => {
107
240
  uri = uri || getMongoURLFromEnv(name);
108
- if (connections[name]) return connections[name];
109
- const client = new import_mongodb.MongoClient(uri, {
110
- retryReads: true
111
- });
112
- let resolveConnected;
113
- const connectionPromise = new Promise((resolve) => {
114
- resolveConnected = resolve;
115
- });
116
- let internalConnectionPromise;
117
- const startConnection = async () => {
118
- if (internalConnectionPromise) {
119
- return await internalConnectionPromise;
120
- }
121
- internalConnectionPromise = connect(client);
122
- allConnectionPromises.push(internalConnectionPromise);
123
- internalConnectionPromise.then((client2) => {
124
- resolveConnected(client2);
125
- });
126
- return await internalConnectionPromise;
127
- };
128
- const dbName = getDBName(uri);
129
- const db = client.db(dbName);
130
- const mongoClient = {
131
- uri,
132
- client,
133
- connectionPromise,
134
- startConnection,
135
- dbName,
136
- db,
137
- connectionName: name
138
- };
139
- connections[name] = mongoClient;
140
- return mongoClient;
241
+ const connection = getExistingConnection(name);
242
+ if (!connection.configured && !requiresExplicitSetup(name)) {
243
+ connection.config(uri, {});
244
+ }
245
+ allConnectionPromises.push(connection.connectionPromise);
246
+ return connection;
141
247
  };
142
248
 
143
249
  // src/types/index.ts
144
250
  var MongoDB = __toESM(require("mongodb"), 1);
145
251
  var import_schema = require("@orion-js/schema");
146
- var Collection = class {
252
+ var BaseCollection = class {
147
253
  name;
148
254
  connectionName;
149
255
  schema;
150
- indexes;
151
256
  generateId;
152
257
  getSchema;
153
258
  db;
154
259
  client;
260
+ /**
261
+ * @deprecated Use getRawCollection() instead. This property is not guaranteed to be defined if the connection has not been started.
262
+ */
155
263
  rawCollection;
264
+ getRawCollection;
156
265
  findOne;
157
266
  find;
158
267
  insertOne;
@@ -184,9 +293,18 @@ var Collection = class {
184
293
  */
185
294
  createIndexes;
186
295
  createIndexesPromise;
296
+ /**
297
+ * @deprecated Use startConnection() instead. This property is not guaranteed to be resolved if the connection is not started.
298
+ * When using async calls startConnection or connectionPromise is no longer needed. Orion will automatically start the connection if it is not already started.
299
+ * Kept for backwards compatibility. startConnection does not re-start the connection if it is already started, so it is safe to use.
300
+ */
187
301
  connectionPromise;
188
302
  startConnection;
189
303
  };
304
+ var Collection = class extends BaseCollection {
305
+ indexes;
306
+ encrypted;
307
+ };
190
308
  function typedId(prefix) {
191
309
  return {
192
310
  ...import_schema.fieldTypes.string,
@@ -1157,6 +1275,7 @@ function getSchema(options) {
1157
1275
  }
1158
1276
 
1159
1277
  // src/createCollection/wrapMethods.ts
1278
+ var import_logger2 = require("@orion-js/logger");
1160
1279
  function wrapMethods(collection) {
1161
1280
  const methodsWithPromises = [
1162
1281
  "findOne",
@@ -1191,6 +1310,14 @@ function wrapMethods(collection) {
1191
1310
  if (typeof collection[methodName2] === "function") {
1192
1311
  collection[methodName2] = (...args) => {
1193
1312
  collection.startConnection();
1313
+ if (!collection.rawCollection) {
1314
+ import_logger2.logger.error("Method called before connection was initialized", {
1315
+ collectionName: collection.name,
1316
+ connectionName: collection.connectionName,
1317
+ methodName: methodName2
1318
+ });
1319
+ throw new Error("Method called before connection was initialized");
1320
+ }
1194
1321
  return originalMethods[methodName2](...args);
1195
1322
  };
1196
1323
  }
@@ -1206,56 +1333,91 @@ function createCollection(options) {
1206
1333
  if (!orionConnection) {
1207
1334
  throw new Error(`The connection to MongoDB "${connectionName}" was not found`);
1208
1335
  }
1209
- const db = orionConnection.db;
1210
- const rawCollection = db.collection(options.name);
1211
1336
  const schema = getSchema(options);
1212
- const collection = {
1337
+ let resolveCollectionPromise;
1338
+ const collectionPromise = new Promise((resolve) => {
1339
+ resolveCollectionPromise = resolve;
1340
+ });
1341
+ const baseCollection = {
1213
1342
  name: options.name,
1214
1343
  connectionName,
1215
1344
  schema,
1216
1345
  indexes: options.indexes || [],
1217
- db,
1218
1346
  client: orionConnection,
1219
- connectionPromise: orionConnection.connectionPromise,
1220
- startConnection: orionConnection.startConnection,
1221
- rawCollection,
1347
+ connectionPromise: collectionPromise,
1348
+ startConnection: () => orionConnection.startConnection(),
1222
1349
  generateId: generateId_default(options),
1350
+ getRawCollection: async () => {
1351
+ await orionConnection.startConnection();
1352
+ return orionConnection.db.collection(options.name);
1353
+ },
1223
1354
  getSchema: () => schema
1224
1355
  };
1225
- collection.findOne = findOne_default(collection);
1226
- collection.find = find_default(collection);
1227
- collection.findOneAndUpdate = findOneAndUpdate_default(collection);
1228
- collection.insertOne = insertOne_default(collection);
1229
- collection.insertMany = insertMany_default(collection);
1230
- collection.insertAndFind = insertAndFind_default(collection);
1231
- collection.updateOne = updateOne_default(collection);
1232
- collection.updateMany = updateMany_default(collection);
1233
- collection.deleteMany = deleteMany_default(collection);
1234
- collection.deleteOne = deleteOne_default(collection);
1235
- collection.upsert = upsert_default(collection);
1236
- collection.estimatedDocumentCount = estimatedDocumentCount_default(collection);
1237
- collection.countDocuments = countDocuments_default(collection);
1238
- collection.updateAndFind = updateAndFind_default(collection);
1239
- collection.updateItem = updateItem_default(collection);
1240
- collection.aggregate = (pipeline, options2) => collection.rawCollection.aggregate(pipeline, options2);
1241
- collection.watch = (pipeline, options2) => collection.rawCollection.watch(pipeline, options2);
1242
- collection.loadData = loadData_default(collection);
1243
- collection.loadById = loadById_default(collection);
1244
- collection.loadOne = loadOne_default(collection);
1245
- collection.loadMany = loadMany_default(collection);
1356
+ const encryptedCollection = {
1357
+ ...baseCollection,
1358
+ getRawCollection: async () => {
1359
+ await orionConnection.startConnection();
1360
+ return orionConnection.encrypted.db.collection(options.name);
1361
+ }
1362
+ };
1363
+ const mainCollection = {
1364
+ ...baseCollection,
1365
+ encrypted: encryptedCollection
1366
+ };
1367
+ const defineCollectionProperties = () => {
1368
+ if (orionConnection.db) {
1369
+ mainCollection.db = orionConnection.db;
1370
+ mainCollection.rawCollection = orionConnection.db.collection(options.name);
1371
+ }
1372
+ if (orionConnection.encrypted.db) {
1373
+ encryptedCollection.db = orionConnection.encrypted.db;
1374
+ encryptedCollection.rawCollection = orionConnection.encrypted.db.collection(options.name);
1375
+ }
1376
+ };
1377
+ defineCollectionProperties();
1378
+ orionConnection.connectionPromise.then(() => {
1379
+ defineCollectionProperties();
1380
+ resolveCollectionPromise(orionConnection.client);
1381
+ });
1382
+ const collections = [mainCollection, encryptedCollection];
1383
+ for (const collection of collections) {
1384
+ collection.findOne = findOne_default(collection);
1385
+ collection.find = find_default(collection);
1386
+ collection.findOneAndUpdate = findOneAndUpdate_default(collection);
1387
+ collection.insertOne = insertOne_default(collection);
1388
+ collection.insertMany = insertMany_default(collection);
1389
+ collection.insertAndFind = insertAndFind_default(collection);
1390
+ collection.updateOne = updateOne_default(collection);
1391
+ collection.updateMany = updateMany_default(collection);
1392
+ collection.deleteMany = deleteMany_default(collection);
1393
+ collection.deleteOne = deleteOne_default(collection);
1394
+ collection.upsert = upsert_default(collection);
1395
+ collection.estimatedDocumentCount = estimatedDocumentCount_default(collection);
1396
+ collection.countDocuments = countDocuments_default(collection);
1397
+ collection.updateAndFind = updateAndFind_default(collection);
1398
+ collection.updateItem = updateItem_default(collection);
1399
+ collection.aggregate = (pipeline, options2) => collection.rawCollection.aggregate(pipeline, options2);
1400
+ collection.watch = (pipeline, options2) => collection.rawCollection.watch(pipeline, options2);
1401
+ collection.loadData = loadData_default(collection);
1402
+ collection.loadById = loadById_default(collection);
1403
+ collection.loadOne = loadOne_default(collection);
1404
+ collection.loadMany = loadMany_default(collection);
1405
+ collection.createIndexes = async () => [];
1406
+ }
1246
1407
  const createIndexes = async () => {
1247
- await orionConnection.connectionPromise;
1248
- const createIndexPromise = loadIndexes(collection);
1408
+ await orionConnection.startConnection();
1409
+ const createIndexPromise = loadIndexes(mainCollection);
1249
1410
  createIndexesPromises.push(createIndexPromise);
1250
- collection.createIndexesPromise = createIndexPromise;
1411
+ mainCollection.createIndexesPromise = createIndexPromise;
1251
1412
  return createIndexPromise;
1252
1413
  };
1253
- collection.createIndexes = createIndexes;
1414
+ mainCollection.createIndexes = createIndexes;
1254
1415
  if (!process.env.DONT_CREATE_INDEXES_AUTOMATICALLY) {
1255
1416
  createIndexes();
1256
1417
  }
1257
- wrapMethods(collection);
1258
- return collection;
1418
+ wrapMethods(mainCollection);
1419
+ wrapMethods(encryptedCollection);
1420
+ return mainCollection;
1259
1421
  }
1260
1422
 
1261
1423
  // src/service/index.ts
@@ -1283,17 +1445,79 @@ function MongoCollection(options) {
1283
1445
  });
1284
1446
  };
1285
1447
  }
1448
+
1449
+ // src/encrypted/getOrCreateEncryptionKey.ts
1450
+ var import_logger3 = require("@orion-js/logger");
1451
+ var import_mongodb3 = require("mongodb");
1452
+ async function getOrCreateEncryptionKey({
1453
+ keyAltName,
1454
+ kmsProvider,
1455
+ masterKey,
1456
+ connectionName = "main",
1457
+ keyVaultDatabase = "encryption",
1458
+ keyVaultCollection = "__keyVault",
1459
+ kmsProviders
1460
+ }) {
1461
+ const keyVaultNamespace = `${keyVaultDatabase}.${keyVaultCollection}`;
1462
+ import_logger3.logger.info("Connecting to database to get or create the encryption key", {
1463
+ keyVaultNamespace,
1464
+ keyAltName
1465
+ });
1466
+ const client = new import_mongodb3.MongoClient(getMongoURLFromEnv(connectionName));
1467
+ await client.connect();
1468
+ const db = client.db(keyVaultDatabase);
1469
+ const collection = db.collection(keyVaultCollection);
1470
+ await collection.createIndex(
1471
+ { keyAltName: 1 },
1472
+ { unique: true, partialFilterExpression: { keyAltName: { $exists: true } } }
1473
+ );
1474
+ const clientEncryption = new import_mongodb3.ClientEncryption(client, {
1475
+ keyVaultNamespace,
1476
+ kmsProviders
1477
+ });
1478
+ const key = await clientEncryption.getKeyByAltName(keyAltName);
1479
+ if (key) {
1480
+ import_logger3.logger.info("Key found on the key vault", {
1481
+ keyVaultNamespace,
1482
+ keyAltName,
1483
+ UUID: key._id
1484
+ });
1485
+ return { key: key._id, keyVaultNamespace };
1486
+ }
1487
+ import_logger3.logger.info("Key not found on the key vault, creating a new one", {
1488
+ keyVaultNamespace,
1489
+ keyAltName
1490
+ });
1491
+ const newKey = await clientEncryption.createDataKey(kmsProvider, {
1492
+ keyAltNames: [keyAltName],
1493
+ ...masterKey ? { masterKey } : {}
1494
+ });
1495
+ import_logger3.logger.info("New encryption key created", {
1496
+ keyVaultNamespace,
1497
+ keyAltName,
1498
+ UUID: newKey
1499
+ });
1500
+ return { key: newKey, keyVaultNamespace };
1501
+ }
1502
+ var ENCRYPTION_ALGORITHMS = {
1503
+ DETERMINISTIC: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic",
1504
+ RANDOM: "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
1505
+ };
1286
1506
  // Annotate the CommonJS export names for ESM import in node:
1287
1507
  0 && (module.exports = {
1508
+ BaseCollection,
1288
1509
  Collection,
1510
+ ENCRYPTION_ALGORITHMS,
1289
1511
  MongoCollection,
1290
1512
  MongoDB,
1291
1513
  Repository,
1292
1514
  allConnectionPromises,
1515
+ configureConnection,
1293
1516
  connections,
1294
1517
  createCollection,
1295
1518
  createIndexesPromises,
1296
1519
  getMongoConnection,
1520
+ getOrCreateEncryptionKey,
1297
1521
  typedId
1298
1522
  });
1299
1523
  //# sourceMappingURL=index.cjs.map