@keyv/mongo 3.1.0 → 6.0.0-alpha.2

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
@@ -31,110 +31,206 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  KeyvMongo: () => KeyvMongo,
34
+ createKeyv: () => createKeyv,
34
35
  default: () => index_default
35
36
  });
36
37
  module.exports = __toCommonJS(index_exports);
37
38
  var import_node_buffer = require("buffer");
38
- var import_node_events = __toESM(require("events"), 1);
39
+ var import_hookified = require("hookified");
40
+ var import_keyv = __toESM(require("keyv"), 1);
39
41
  var import_mongodb = require("mongodb");
40
- var keyvMongoKeys = /* @__PURE__ */ new Set([
41
- "url",
42
- "collection",
43
- "namespace",
44
- "serialize",
45
- "deserialize",
46
- "uri",
47
- "useGridFS",
48
- "dialect",
49
- "db"
50
- ]);
51
- var KeyvMongo = class extends import_node_events.default {
52
- ttlSupport = false;
53
- opts;
42
+ var KeyvMongo = class extends import_hookified.Hookified {
43
+ /**
44
+ * The MongoDB connection URI.
45
+ * @default 'mongodb://127.0.0.1:27017'
46
+ */
47
+ _url = "mongodb://127.0.0.1:27017";
48
+ /**
49
+ * The collection name used for storage.
50
+ * @default 'keyv'
51
+ */
52
+ _collection = "keyv";
53
+ /**
54
+ * The namespace used to prefix keys for multi-tenant separation.
55
+ */
56
+ _namespace;
57
+ /**
58
+ * Whether to use GridFS for storing values.
59
+ * @default false
60
+ */
61
+ _useGridFS = false;
62
+ /**
63
+ * The database name for the MongoDB connection.
64
+ * @default undefined
65
+ */
66
+ _db;
67
+ /**
68
+ * The MongoDB read preference for GridFS operations.
69
+ * @default undefined
70
+ */
71
+ _readPreference;
72
+ /**
73
+ * Additional MongoClientOptions passed through to the MongoDB driver.
74
+ */
75
+ _mongoOptions = {};
76
+ /**
77
+ * Promise that resolves to the MongoDB connection details.
78
+ */
54
79
  connect;
55
- namespace;
80
+ /**
81
+ * Get the MongoDB connection URI.
82
+ * @default 'mongodb://127.0.0.1:27017'
83
+ */
84
+ get url() {
85
+ return this._url;
86
+ }
87
+ /**
88
+ * Set the MongoDB connection URI.
89
+ */
90
+ set url(value) {
91
+ this._url = value;
92
+ }
93
+ /**
94
+ * Get the collection name used for storage.
95
+ * @default 'keyv'
96
+ */
97
+ get collection() {
98
+ return this._collection;
99
+ }
100
+ /**
101
+ * Set the collection name used for storage.
102
+ */
103
+ set collection(value) {
104
+ this._collection = value;
105
+ }
106
+ /**
107
+ * Get the namespace for the adapter. If undefined, no namespace prefix is applied.
108
+ */
109
+ get namespace() {
110
+ return this._namespace;
111
+ }
112
+ /**
113
+ * Set the namespace for the adapter. Used for key prefixing and scoping operations like `clear()`.
114
+ */
115
+ set namespace(value) {
116
+ this._namespace = value;
117
+ }
118
+ /**
119
+ * Get whether GridFS is used for storing values. This is read-only and can only be set via the constructor
120
+ * because the MongoDB connection shape differs between GridFS and standard modes.
121
+ * @default false
122
+ */
123
+ get useGridFS() {
124
+ return this._useGridFS;
125
+ }
126
+ /**
127
+ * Get the database name for the MongoDB connection.
128
+ */
129
+ get db() {
130
+ return this._db;
131
+ }
132
+ /**
133
+ * Set the database name for the MongoDB connection.
134
+ */
135
+ set db(value) {
136
+ this._db = value;
137
+ }
138
+ /**
139
+ * Get the MongoDB read preference for GridFS operations.
140
+ */
141
+ get readPreference() {
142
+ return this._readPreference;
143
+ }
144
+ /**
145
+ * Set the MongoDB read preference for GridFS operations.
146
+ */
147
+ set readPreference(value) {
148
+ this._readPreference = value;
149
+ }
150
+ /**
151
+ * Get the options for the adapter. This is provided for backward compatibility.
152
+ */
153
+ // biome-ignore lint/suspicious/noExplicitAny: type format
154
+ get opts() {
155
+ return {
156
+ url: this._url,
157
+ uri: this._url,
158
+ collection: this._collection,
159
+ useGridFS: this._useGridFS,
160
+ db: this._db,
161
+ readPreference: this._readPreference,
162
+ dialect: "mongo",
163
+ ...this._mongoOptions
164
+ };
165
+ }
166
+ /**
167
+ * Creates a new KeyvMongo instance.
168
+ * @param url - Configuration options, connection URI string, or undefined for defaults.
169
+ * @param options - Additional configuration options that override the first parameter.
170
+ */
56
171
  constructor(url, options) {
57
- super();
58
- url ??= {};
172
+ super({ throwOnEmptyListeners: false });
173
+ let mergedOptions = {};
59
174
  if (typeof url === "string") {
60
- url = { url };
175
+ this._url = url;
176
+ if (options) {
177
+ mergedOptions = options;
178
+ }
179
+ } else if (url) {
180
+ mergedOptions = { ...url, ...options };
181
+ } else if (options) {
182
+ mergedOptions = options;
61
183
  }
62
- if (url.uri) {
63
- url = { url: url.uri, ...url };
184
+ if (mergedOptions.uri !== void 0) {
185
+ this._url = mergedOptions.uri;
64
186
  }
65
- this.opts = {
66
- url: "mongodb://127.0.0.1:27017",
67
- collection: "keyv",
68
- ...url,
69
- ...options
70
- };
71
- delete this.opts.emitErrors;
72
- const mongoOptions = Object.fromEntries(
73
- Object.entries(this.opts).filter(([k]) => !keyvMongoKeys.has(k))
74
- );
75
- this.opts = Object.fromEntries(
76
- Object.entries(this.opts).filter(([k]) => keyvMongoKeys.has(k))
77
- );
78
- this.connect = new Promise(async (resolve, _reject) => {
79
- try {
80
- let url2 = "";
81
- if (this.opts.url) {
82
- url2 = this.opts.url;
83
- }
84
- const client = new import_mongodb.MongoClient(url2, mongoOptions);
85
- await client.connect();
86
- const database = client.db(this.opts.db);
87
- if (this.opts.useGridFS) {
88
- const bucket = new import_mongodb.GridFSBucket(database, {
89
- readPreference: this.opts.readPreference,
90
- bucketName: this.opts.collection
91
- });
92
- const store = database.collection(`${this.opts.collection}.files`);
93
- await store.createIndex({ uploadDate: -1 });
94
- await store.createIndex({ "metadata.expiresAt": 1 });
95
- await store.createIndex({ "metadata.lastAccessed": 1 });
96
- await store.createIndex({ "metadata.filename": 1 });
97
- resolve({
98
- bucket,
99
- store,
100
- db: database,
101
- mongoClient: client
102
- });
103
- } else {
104
- let collection = "keyv";
105
- if (this.opts.collection) {
106
- collection = this.opts.collection;
107
- }
108
- const store = database.collection(collection);
109
- await store.createIndex(
110
- { key: 1 },
111
- { unique: true, background: true }
112
- );
113
- await store.createIndex(
114
- { expiresAt: 1 },
115
- { expireAfterSeconds: 0, background: true }
116
- );
117
- resolve({ store, mongoClient: client });
118
- }
119
- } catch (error) {
120
- this.emit("error", error);
121
- }
122
- });
187
+ if (mergedOptions.url !== void 0) {
188
+ this._url = mergedOptions.url;
189
+ }
190
+ if (mergedOptions.collection !== void 0) {
191
+ this._collection = mergedOptions.collection;
192
+ }
193
+ if (mergedOptions.namespace !== void 0) {
194
+ this._namespace = mergedOptions.namespace;
195
+ }
196
+ if (mergedOptions.useGridFS !== void 0) {
197
+ this._useGridFS = mergedOptions.useGridFS;
198
+ }
199
+ if (mergedOptions.db !== void 0) {
200
+ this._db = mergedOptions.db;
201
+ }
202
+ if (mergedOptions.readPreference !== void 0) {
203
+ this._readPreference = mergedOptions.readPreference;
204
+ }
205
+ this._mongoOptions = this.extractMongoOptions(mergedOptions);
206
+ this.connect = this.initConnection();
123
207
  }
208
+ /**
209
+ * Get a value from the store by key. In GridFS mode, also updates the `lastAccessed` timestamp.
210
+ * @param key - The key to retrieve.
211
+ * @returns The stored value, or `undefined` if the key does not exist.
212
+ */
124
213
  async get(key) {
125
214
  const client = await this.connect;
126
- if (this.opts.useGridFS) {
215
+ const strippedKey = this.removeKeyPrefix(key);
216
+ const ns = this.getNamespaceValue();
217
+ if (this._useGridFS) {
218
+ const file = await client.store.findOne({
219
+ filename: { $eq: strippedKey },
220
+ "metadata.namespace": { $eq: ns }
221
+ });
222
+ if (!file) {
223
+ return void 0;
224
+ }
127
225
  await client.store.updateOne(
128
- {
129
- filename: String(key)
130
- },
226
+ { _id: { $eq: file._id } },
131
227
  {
132
228
  $set: {
133
229
  "metadata.lastAccessed": /* @__PURE__ */ new Date()
134
230
  }
135
231
  }
136
232
  );
137
- const stream = client.bucket.openDownloadStreamByName(key);
233
+ const stream = client.bucket.openDownloadStream(file._id);
138
234
  return new Promise((resolve) => {
139
235
  const resp = [];
140
236
  stream.on("error", () => {
@@ -149,14 +245,23 @@ var KeyvMongo = class extends import_node_events.default {
149
245
  });
150
246
  });
151
247
  }
152
- const document = await client.store.findOne({ key: { $eq: key } });
248
+ const document = await client.store.findOne({
249
+ key: { $eq: strippedKey },
250
+ namespace: { $eq: ns }
251
+ });
153
252
  if (!document) {
154
253
  return void 0;
155
254
  }
156
255
  return document.value;
157
256
  }
257
+ /**
258
+ * Get multiple values from the store at once. In standard mode, uses a single query with the `$in` operator.
259
+ * In GridFS mode, each key is fetched individually in parallel.
260
+ * @param keys - Array of keys to retrieve.
261
+ * @returns Array of values in the same order as the input keys. Missing keys return `undefined`.
262
+ */
158
263
  async getMany(keys) {
159
- if (this.opts.useGridFS) {
264
+ if (this._useGridFS) {
160
265
  const promises = [];
161
266
  for (const key of keys) {
162
267
  promises.push(this.get(key));
@@ -169,30 +274,38 @@ var KeyvMongo = class extends import_node_events.default {
169
274
  return data;
170
275
  }
171
276
  const connect = await this.connect;
172
- const values = (
173
- // @ts-expect-error need to fix this `s`
174
- await connect.store.s.db.collection(this.opts.collection).find({ key: { $in: keys } }).project({ _id: 0, value: 1, key: 1 }).toArray()
175
- );
176
- const results = [...keys];
177
- let i = 0;
178
- for (const key of keys) {
277
+ const strippedKeys = keys.map((k) => this.removeKeyPrefix(k));
278
+ const ns = this.getNamespaceValue();
279
+ const values = await connect.store.find({ key: { $in: strippedKeys }, namespace: { $eq: ns } }).project({ _id: 0, value: 1, key: 1 }).toArray();
280
+ const results = [];
281
+ for (const key of strippedKeys) {
179
282
  const rowIndex = values.findIndex(
180
283
  (row) => row.key === key
181
284
  );
182
- results[i] = rowIndex > -1 ? values[rowIndex].value : void 0;
183
- i++;
285
+ results.push(
286
+ rowIndex > -1 ? values[rowIndex].value : void 0
287
+ );
184
288
  }
185
289
  return results;
186
290
  }
291
+ /**
292
+ * Set a value in the store.
293
+ * @param key - The key to set.
294
+ * @param value - The value to store.
295
+ * @param ttl - Time to live in milliseconds. If specified, the key will expire after this duration.
296
+ */
187
297
  // biome-ignore lint/suspicious/noExplicitAny: type format
188
298
  async set(key, value, ttl) {
189
299
  const expiresAt = typeof ttl === "number" ? new Date(Date.now() + ttl) : null;
190
- if (this.opts.useGridFS) {
300
+ const strippedKey = this.removeKeyPrefix(key);
301
+ const ns = this.getNamespaceValue();
302
+ if (this._useGridFS) {
191
303
  const client2 = await this.connect;
192
- const stream = client2.bucket.openUploadStream(key, {
304
+ const stream = client2.bucket.openUploadStream(strippedKey, {
193
305
  metadata: {
194
306
  expiresAt,
195
- lastAccessed: /* @__PURE__ */ new Date()
307
+ lastAccessed: /* @__PURE__ */ new Date(),
308
+ namespace: ns
196
309
  }
197
310
  });
198
311
  return new Promise((resolve) => {
@@ -204,40 +317,96 @@ var KeyvMongo = class extends import_node_events.default {
204
317
  }
205
318
  const client = await this.connect;
206
319
  await client.store.updateOne(
207
- { key: { $eq: key } },
208
- { $set: { key, value, expiresAt } },
320
+ { key: { $eq: strippedKey }, namespace: { $eq: ns } },
321
+ { $set: { key: strippedKey, value, namespace: ns, expiresAt } },
209
322
  { upsert: true }
210
323
  );
211
324
  }
325
+ /**
326
+ * Set multiple values in the store at once. In standard mode, uses a single `bulkWrite` operation.
327
+ * In GridFS mode, each entry is set individually in parallel.
328
+ * @param entries - Array of entries to set. Each entry has a `key`, `value`, and optional `ttl` in milliseconds.
329
+ */
330
+ async setMany(entries) {
331
+ if (this._useGridFS) {
332
+ await Promise.all(
333
+ entries.map(async ({ key, value, ttl }) => this.set(key, value, ttl))
334
+ );
335
+ return;
336
+ }
337
+ const client = await this.connect;
338
+ const ns = this.getNamespaceValue();
339
+ const operations = entries.map(({ key, value, ttl }) => {
340
+ const strippedKey = this.removeKeyPrefix(key);
341
+ const expiresAt = typeof ttl === "number" ? new Date(Date.now() + ttl) : null;
342
+ return {
343
+ updateOne: {
344
+ filter: { key: { $eq: strippedKey }, namespace: { $eq: ns } },
345
+ update: {
346
+ $set: { key: strippedKey, value, namespace: ns, expiresAt }
347
+ },
348
+ upsert: true
349
+ }
350
+ };
351
+ });
352
+ await client.store.bulkWrite(operations);
353
+ }
354
+ /**
355
+ * Delete a key from the store.
356
+ * @param key - The key to delete.
357
+ * @returns `true` if the key was deleted, `false` if the key was not found.
358
+ */
212
359
  async delete(key) {
213
360
  if (typeof key !== "string") {
214
361
  return false;
215
362
  }
216
363
  const client = await this.connect;
217
- if (this.opts.useGridFS) {
364
+ const strippedKey = this.removeKeyPrefix(key);
365
+ const ns = this.getNamespaceValue();
366
+ if (this._useGridFS) {
218
367
  try {
219
368
  const connection = client.db;
220
369
  const bucket = new import_mongodb.GridFSBucket(connection, {
221
- bucketName: this.opts.collection
370
+ bucketName: this._collection
222
371
  });
223
- const files = await bucket.find({ filename: key }).toArray();
372
+ const files = await bucket.find({
373
+ filename: { $eq: strippedKey },
374
+ "metadata.namespace": { $eq: ns }
375
+ }).toArray();
376
+ if (files.length === 0) {
377
+ return false;
378
+ }
224
379
  await client.bucket.delete(files[0]._id);
225
380
  return true;
226
381
  } catch {
227
382
  return false;
228
383
  }
229
384
  }
230
- const object = await client.store.deleteOne({ key: { $eq: key } });
385
+ const object = await client.store.deleteOne({
386
+ key: { $eq: strippedKey },
387
+ namespace: { $eq: ns }
388
+ });
231
389
  return object.deletedCount > 0;
232
390
  }
391
+ /**
392
+ * Delete multiple keys from the store at once. In standard mode, uses a single query with the `$in` operator.
393
+ * In GridFS mode, all matching files are found and deleted in parallel.
394
+ * @param keys - Array of keys to delete.
395
+ * @returns `true` if any keys were deleted, `false` if none were found.
396
+ */
233
397
  async deleteMany(keys) {
234
398
  const client = await this.connect;
235
- if (this.opts.useGridFS) {
399
+ const strippedKeys = keys.map((k) => this.removeKeyPrefix(k));
400
+ const ns = this.getNamespaceValue();
401
+ if (this._useGridFS) {
236
402
  const connection = client.db;
237
403
  const bucket = new import_mongodb.GridFSBucket(connection, {
238
- bucketName: this.opts.collection
404
+ bucketName: this._collection
239
405
  });
240
- const files = await bucket.find({ filename: { $in: keys } }).toArray();
406
+ const files = await bucket.find({
407
+ filename: { $in: strippedKeys },
408
+ "metadata.namespace": { $eq: ns }
409
+ }).toArray();
241
410
  if (files.length === 0) {
242
411
  return false;
243
412
  }
@@ -247,31 +416,56 @@ var KeyvMongo = class extends import_node_events.default {
247
416
  );
248
417
  return true;
249
418
  }
250
- const object = await client.store.deleteMany({ key: { $in: keys } });
419
+ const object = await client.store.deleteMany({
420
+ key: { $in: strippedKeys },
421
+ namespace: { $eq: ns }
422
+ });
251
423
  return object.deletedCount > 0;
252
424
  }
425
+ /**
426
+ * Delete all keys in the current namespace.
427
+ */
253
428
  async clear() {
254
429
  const client = await this.connect;
255
- if (this.opts.useGridFS) {
256
- await client.bucket.drop();
430
+ const ns = this.getNamespaceValue();
431
+ if (this._useGridFS) {
432
+ const connection = client.db;
433
+ const bucket = new import_mongodb.GridFSBucket(connection, {
434
+ bucketName: this._collection
435
+ });
436
+ const files = await bucket.find({
437
+ "metadata.namespace": { $eq: ns }
438
+ }).toArray();
439
+ await Promise.all(
440
+ // biome-ignore lint/style/noNonNullAssertion: need to fix
441
+ files.map(async (file) => client.bucket.delete(file._id))
442
+ );
443
+ return;
257
444
  }
258
445
  await client.store.deleteMany({
259
- key: { $regex: this.namespace ? `^${this.namespace}:*` : "" }
446
+ namespace: { $eq: ns }
260
447
  });
261
448
  }
449
+ /**
450
+ * Remove all expired files from GridFS. This method only works in GridFS mode
451
+ * and is a no-op that returns `false` in standard mode.
452
+ * @returns `true` if running in GridFS mode, `false` otherwise.
453
+ */
262
454
  async clearExpired() {
263
- if (!this.opts.useGridFS) {
455
+ if (!this._useGridFS) {
264
456
  return false;
265
457
  }
458
+ const ns = this.getNamespaceValue();
266
459
  return this.connect.then(async (client) => {
267
460
  const connection = client.db;
268
461
  const bucket = new import_mongodb.GridFSBucket(connection, {
269
- bucketName: this.opts.collection
462
+ bucketName: this._collection
270
463
  });
271
464
  return bucket.find({
272
465
  "metadata.expiresAt": {
273
466
  $lte: new Date(Date.now())
274
- }
467
+ },
468
+ "metadata.namespace": { $eq: ns }
275
469
  }).toArray().then(
276
470
  async (expiredFiles) => Promise.all(
277
471
  // biome-ignore lint/style/noNonNullAssertion: need to fix
@@ -280,19 +474,27 @@ var KeyvMongo = class extends import_node_events.default {
280
474
  );
281
475
  });
282
476
  }
477
+ /**
478
+ * Remove all GridFS files that have not been accessed for the specified duration. This method only works
479
+ * in GridFS mode and is a no-op that returns `false` in standard mode.
480
+ * @param seconds - The number of seconds of inactivity after which files should be removed.
481
+ * @returns `true` if running in GridFS mode, `false` otherwise.
482
+ */
283
483
  async clearUnusedFor(seconds) {
284
- if (!this.opts.useGridFS) {
484
+ if (!this._useGridFS) {
285
485
  return false;
286
486
  }
487
+ const ns = this.getNamespaceValue();
287
488
  const client = await this.connect;
288
489
  const connection = client.db;
289
490
  const bucket = new import_mongodb.GridFSBucket(connection, {
290
- bucketName: this.opts.collection
491
+ bucketName: this._collection
291
492
  });
292
493
  const lastAccessedFiles = await bucket.find({
293
494
  "metadata.lastAccessed": {
294
495
  $lte: new Date(Date.now() - seconds * 1e3)
295
- }
496
+ },
497
+ "metadata.namespace": { $eq: ns }
296
498
  }).toArray();
297
499
  await Promise.all(
298
500
  // biome-ignore lint/style/noNonNullAssertion: need to fix
@@ -300,33 +502,193 @@ var KeyvMongo = class extends import_node_events.default {
300
502
  );
301
503
  return true;
302
504
  }
505
+ /**
506
+ * Iterate over all key-value pairs in the store matching the given namespace.
507
+ * @param namespace - The namespace to iterate over. When used through Keyv, this is passed automatically.
508
+ * @yields `[key, value]` pairs as an async generator.
509
+ */
303
510
  async *iterator(namespace) {
304
511
  const client = await this.connect;
305
- const regexp = new RegExp(`^${namespace ? namespace + ":" : ".*"}`);
306
- const iterator = this.opts.useGridFS ? client.store.find({
307
- filename: regexp
308
- }).map(async (x) => [
309
- x.filename,
310
- await this.get(x.filename)
311
- ]) : client.store.find({
312
- key: regexp
313
- }).map((x) => [x.key, x.value]);
512
+ const namespaceValue = namespace ?? "";
513
+ if (this._useGridFS) {
514
+ const gridIterator = client.store.find({ "metadata.namespace": { $eq: namespaceValue } }).map(async (x) => {
515
+ const prefixedKey = namespace ? `${namespace}:${x.filename}` : x.filename;
516
+ const stream = client.bucket.openDownloadStream(x._id);
517
+ const data = await new Promise((resolve) => {
518
+ const resp = [];
519
+ stream.on("error", () => {
520
+ resolve(void 0);
521
+ });
522
+ stream.on("end", () => {
523
+ resolve(import_node_buffer.Buffer.concat(resp).toString("utf8"));
524
+ });
525
+ stream.on("data", (chunk) => {
526
+ resp.push(chunk);
527
+ });
528
+ });
529
+ return [prefixedKey, data];
530
+ });
531
+ yield* gridIterator;
532
+ return;
533
+ }
534
+ const iterator = client.store.find({ namespace: { $eq: namespaceValue } }).map((x) => {
535
+ const prefixedKey = namespace ? `${namespace}:${x.key}` : x.key;
536
+ return [prefixedKey, x.value];
537
+ });
314
538
  yield* iterator;
315
539
  }
540
+ /**
541
+ * Check if a key exists in the store.
542
+ * @param key - The key to check.
543
+ * @returns `true` if the key exists, `false` otherwise.
544
+ */
316
545
  async has(key) {
317
546
  const client = await this.connect;
318
- const filter = { [this.opts.useGridFS ? "filename" : "key"]: { $eq: key } };
319
- const document = await client.store.count(filter);
547
+ const strippedKey = this.removeKeyPrefix(key);
548
+ const ns = this.getNamespaceValue();
549
+ if (this._useGridFS) {
550
+ const document2 = await client.store.count({
551
+ filename: { $eq: strippedKey },
552
+ "metadata.namespace": { $eq: ns }
553
+ });
554
+ return document2 !== 0;
555
+ }
556
+ const document = await client.store.count({
557
+ key: { $eq: strippedKey },
558
+ namespace: { $eq: ns }
559
+ });
320
560
  return document !== 0;
321
561
  }
562
+ /**
563
+ * Check if multiple keys exist in the store at once. Uses a single query with the `$in` operator.
564
+ * @param keys - Array of keys to check.
565
+ * @returns Array of booleans in the same order as the input keys.
566
+ */
567
+ async hasMany(keys) {
568
+ const client = await this.connect;
569
+ const strippedKeys = keys.map((k) => this.removeKeyPrefix(k));
570
+ const ns = this.getNamespaceValue();
571
+ if (this._useGridFS) {
572
+ const files = await client.store.find({
573
+ filename: { $in: strippedKeys },
574
+ "metadata.namespace": { $eq: ns }
575
+ }).project({ filename: 1 }).toArray();
576
+ const existingKeys2 = new Set(files.map((f) => f.filename));
577
+ return strippedKeys.map((key) => existingKeys2.has(key));
578
+ }
579
+ const docs = await client.store.find({
580
+ key: { $in: strippedKeys },
581
+ namespace: { $eq: ns }
582
+ }).project({ key: 1 }).toArray();
583
+ const existingKeys = new Set(docs.map((d) => d.key));
584
+ return strippedKeys.map((key) => existingKeys.has(key));
585
+ }
586
+ /**
587
+ * Close the MongoDB connection.
588
+ */
322
589
  async disconnect() {
323
590
  const client = await this.connect;
324
591
  await client.mongoClient.close();
325
592
  }
593
+ /**
594
+ * Strips the namespace prefix from a key that was added by the Keyv core.
595
+ * For example, if namespace is "ns" and key is "ns:foo", returns "foo".
596
+ */
597
+ removeKeyPrefix(key) {
598
+ if (this._namespace && key.startsWith(`${this._namespace}:`)) {
599
+ return key.slice(this._namespace.length + 1);
600
+ }
601
+ return key;
602
+ }
603
+ /**
604
+ * Returns the namespace value for query filters.
605
+ * Returns empty string when no namespace is set.
606
+ */
607
+ getNamespaceValue() {
608
+ return this._namespace ?? "";
609
+ }
610
+ /**
611
+ * Extracts MongoDB driver options from the provided options, filtering out Keyv-specific properties.
612
+ */
613
+ extractMongoOptions(options) {
614
+ const keyvKeys = /* @__PURE__ */ new Set([
615
+ "url",
616
+ "collection",
617
+ "namespace",
618
+ "serialize",
619
+ "deserialize",
620
+ "uri",
621
+ "useGridFS",
622
+ "dialect",
623
+ "db",
624
+ "readPreference",
625
+ "emitErrors"
626
+ ]);
627
+ const mongoOptions = {};
628
+ for (const [key, value] of Object.entries(options)) {
629
+ if (!keyvKeys.has(key)) {
630
+ mongoOptions[key] = value;
631
+ }
632
+ }
633
+ return mongoOptions;
634
+ }
635
+ /**
636
+ * Initializes the MongoDB connection and sets up indexes.
637
+ */
638
+ initConnection() {
639
+ return new Promise(async (resolve, _reject) => {
640
+ try {
641
+ const client = new import_mongodb.MongoClient(this._url, this._mongoOptions);
642
+ await client.connect();
643
+ const database = client.db(this._db);
644
+ if (this._useGridFS) {
645
+ const bucket = new import_mongodb.GridFSBucket(database, {
646
+ readPreference: this._readPreference,
647
+ bucketName: this._collection
648
+ });
649
+ const store = database.collection(`${this._collection}.files`);
650
+ await store.createIndex({ uploadDate: -1 });
651
+ await store.createIndex({ "metadata.expiresAt": 1 });
652
+ await store.createIndex({ "metadata.lastAccessed": 1 });
653
+ await store.createIndex({ "metadata.filename": 1 });
654
+ await store.createIndex({ "metadata.namespace": 1 });
655
+ resolve({
656
+ bucket,
657
+ store,
658
+ db: database,
659
+ mongoClient: client
660
+ });
661
+ } else {
662
+ const store = database.collection(this._collection);
663
+ try {
664
+ await store.dropIndex("key_1");
665
+ } catch {
666
+ }
667
+ await store.createIndex(
668
+ { key: 1, namespace: 1 },
669
+ { unique: true, background: true }
670
+ );
671
+ await store.createIndex(
672
+ { expiresAt: 1 },
673
+ { expireAfterSeconds: 0, background: true }
674
+ );
675
+ resolve({ store, mongoClient: client });
676
+ }
677
+ } catch (error) {
678
+ this.emit("error", error);
679
+ }
680
+ });
681
+ }
682
+ };
683
+ var createKeyv = (options) => {
684
+ const store = new KeyvMongo(options);
685
+ const namespace = typeof options === "object" ? options?.namespace : void 0;
686
+ return new import_keyv.default({ store, namespace });
326
687
  };
327
688
  var index_default = KeyvMongo;
328
689
  // Annotate the CommonJS export names for ESM import in node:
329
690
  0 && (module.exports = {
330
- KeyvMongo
691
+ KeyvMongo,
692
+ createKeyv
331
693
  });
332
694
  /* v8 ignore next -- @preserve */