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